-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSurvivingInvariantTests.cs
More file actions
144 lines (122 loc) · 4.2 KB
/
Copy pathSurvivingInvariantTests.cs
File metadata and controls
144 lines (122 loc) · 4.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
namespace Settex.Core.Tests.Compilation;
using System.Text.Json.Nodes;
using Settex.Compilation;
using Settex.Core.Evaluation;
using TUnit.Assertions;
using TUnit.Assertions.Extensions;
using TUnit.Core;
/// <summary>
/// Four invariants that mutation testing showed no test protected: each could be
/// removed from the production code with all tests still green.
/// </summary>
public class SurvivingInvariantTests
{
[Test]
public async Task Compile_LogicalAnd_DoesNotEvaluateItsRightSideWhenTheLeftIsFalseAsync()
{
// Short-circuiting is written and commented in the evaluator but nothing checked
// it. Observable only when the right side would fail: an undefined variable here
// must never be reached.
const string source = """
settings {
A = "yes" if false and undefinedVariable
}
""";
var (result, tempDir) = Compile(source);
try
{
await Assert.That(result.Success).IsTrue();
}
finally
{
Cleanup(tempDir);
}
}
[Test]
public async Task Compile_LogicalOr_DoesNotEvaluateItsRightSideWhenTheLeftIsTrueAsync()
{
const string source = """
settings {
A = "yes" if true or undefinedVariable
}
""";
var (result, tempDir) = Compile(source);
try
{
await Assert.That(result.Success).IsTrue();
}
finally
{
Cleanup(tempDir);
}
}
[Test]
public async Task Compile_TwoLetsOfTheSameName_AreRejectedAsync()
{
// A deliberate validation with a user-facing message, and no test anywhere.
var (result, tempDir) = Compile("let a = 1\nlet a = 2\nsettings { A = a }");
try
{
await Assert.That(result.Success).IsFalse();
await Assert.That(result.Errors.Any(e => e.Message.Contains("Duplicate"))).IsTrue();
}
finally
{
Cleanup(tempDir);
}
}
[Test]
public async Task Compile_GeneratedFiles_UseLineFeedEndingsAsync()
{
// The writer normalises to LF on purpose. Changing it would churn every diff on
// every platform, and nothing pinned it.
var (result, tempDir) = Compile("settings {\n A = 1\n B = 2\n}");
try
{
await Assert.That(result.Success).IsTrue();
var json = await File.ReadAllTextAsync(Path.Combine(tempDir, "output", "appsettings.json"));
await Assert.That(json).Contains("\n");
await Assert.That(json).DoesNotContain("\r\n");
}
finally
{
Cleanup(tempDir);
}
}
[Test]
public async Task Analyze_ArrayEntriesDifferingOnlyInCase_AreOneEntryAsync()
{
// The flattened entry keys are compared case-insensitively, like every other
// configuration key. Comparing them ordinally would report a field as leaking
// when the override does redefine it, only with different casing.
var model = new SettingsModel(
new JsonObject
{
["Svcs"] = new JsonArray(new JsonObject { ["Name"] = "a", ["Port"] = 1 }),
},
new()
{
["Dev"] = new JsonObject
{
["Svcs"] = new JsonArray(new JsonObject { ["name"] = "b", ["port"] = 2 }),
},
});
var diagnostics = ArrayLayeringAnalyzer.Analyze(model);
await Assert.That(diagnostics).IsEmpty();
}
private static (CompilationResult Result, string TempDir) Compile(string source)
{
var tempDir = Path.Combine(Path.GetTempPath(), "SettexInvariants", Guid.NewGuid().ToString());
Directory.CreateDirectory(tempDir);
var sourceFile = Path.Combine(tempDir, "appsettings.settex");
File.WriteAllText(sourceFile, source);
return (new SettexCompiler().Compile(sourceFile, Path.Combine(tempDir, "output")), tempDir);
}
private static void Cleanup(string directory)
{
if (Directory.Exists(directory))
{
Directory.Delete(directory, recursive: true);
}
}
}