Skip to content

Commit 5e73326

Browse files
committed
src: Add dogfooding integ tests
1 parent b1eca3f commit 5e73326

22 files changed

Lines changed: 287 additions & 1 deletion

.claude/settings.local.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"Bash(for:*)",
2626
"Bash(do dotnet test --no-build)",
2727
"Bash(dotnet run:*)",
28-
"Bash(dotnet restore:*)"
28+
"Bash(dotnet restore:*)",
29+
"mcp__Desktop_Commander__create_directory"
2930
]
3031
}
3132
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
using System.Reflection;
2+
using Xunit;
3+
using Xunit.Abstractions;
4+
5+
namespace Sharpy.Compiler.Tests.Integration;
6+
7+
/// <summary>
8+
/// File-based integration tests that discover .spy test files and their expected outputs.
9+
///
10+
/// Test files are organized in the TestFixtures directory:
11+
/// TestFixtures/
12+
/// feature_name/
13+
/// test_name.spy - Sharpy source code
14+
/// test_name.expected - Expected stdout output
15+
/// test_name.error - (optional) Expected compilation error substring
16+
///
17+
/// A test passes if:
18+
/// - The .spy file compiles and executes successfully
19+
/// - The stdout matches the contents of .expected exactly
20+
///
21+
/// Error tests (when .error file exists):
22+
/// - The .spy file should fail to compile
23+
/// - The error message should contain the text in .error
24+
/// </summary>
25+
public class FileBasedIntegrationTests : IntegrationTestBase
26+
{
27+
private static readonly string FixturesPath;
28+
29+
static FileBasedIntegrationTests()
30+
{
31+
// Find the TestFixtures directory relative to the test assembly
32+
var assemblyLocation = Assembly.GetExecutingAssembly().Location;
33+
var assemblyDir = Path.GetDirectoryName(assemblyLocation)!;
34+
35+
// Navigate from bin/Debug/net10.0 to the source directory
36+
FixturesPath = Path.GetFullPath(Path.Combine(
37+
assemblyDir, "..", "..", "..", "Integration", "TestFixtures"));
38+
}
39+
40+
public FileBasedIntegrationTests(ITestOutputHelper output) : base(output)
41+
{
42+
}
43+
44+
/// <summary>
45+
/// Discovers all test fixtures by scanning the TestFixtures directory.
46+
/// </summary>
47+
public static IEnumerable<object[]> GetTestFixtures()
48+
{
49+
if (!Directory.Exists(FixturesPath))
50+
{
51+
yield break;
52+
}
53+
54+
// Find all .spy files recursively
55+
foreach (var spyFile in Directory.EnumerateFiles(FixturesPath, "*.spy", SearchOption.AllDirectories))
56+
{
57+
var relativePath = Path.GetRelativePath(FixturesPath, spyFile);
58+
var testName = Path.ChangeExtension(relativePath, null).Replace(Path.DirectorySeparatorChar, '/');
59+
yield return new object[] { testName, spyFile };
60+
}
61+
}
62+
63+
[Theory]
64+
[MemberData(nameof(GetTestFixtures))]
65+
public void RunTestFixture(string testName, string spyFilePath)
66+
{
67+
Output.WriteLine($"Running test: {testName}");
68+
Output.WriteLine($"Source file: {spyFilePath}");
69+
70+
// Read the source file
71+
var source = File.ReadAllText(spyFilePath);
72+
Output.WriteLine("=== Sharpy Source ===");
73+
Output.WriteLine(source);
74+
Output.WriteLine("=====================");
75+
76+
// Check if this is an error test
77+
var errorFilePath = Path.ChangeExtension(spyFilePath, ".error");
78+
var isErrorTest = File.Exists(errorFilePath);
79+
80+
// Execute the test
81+
var result = CompileAndExecute(source, Path.GetFileName(spyFilePath));
82+
83+
if (isErrorTest)
84+
{
85+
// Error test: compilation should fail
86+
var expectedError = File.ReadAllText(errorFilePath).Trim();
87+
Output.WriteLine($"Expected error: {expectedError}");
88+
89+
Assert.False(result.Success,
90+
$"Expected compilation to fail, but it succeeded. Output: {result.StandardOutput}");
91+
92+
var actualErrors = string.Join("\n", result.CompilationErrors);
93+
Assert.Contains(expectedError, actualErrors, StringComparison.OrdinalIgnoreCase);
94+
}
95+
else
96+
{
97+
// Success test: compilation should succeed and output should match
98+
var expectedFilePath = Path.ChangeExtension(spyFilePath, ".expected");
99+
100+
Assert.True(File.Exists(expectedFilePath),
101+
$"Missing expected output file: {expectedFilePath}");
102+
103+
var expectedOutput = File.ReadAllText(expectedFilePath);
104+
105+
Assert.True(result.Success,
106+
$"Compilation failed: {string.Join("\n", result.CompilationErrors)}");
107+
108+
Assert.Equal(expectedOutput, result.StandardOutput);
109+
}
110+
}
111+
112+
/// <summary>
113+
/// Verifies that the test fixtures directory exists and contains at least one test.
114+
/// This is a sanity check to ensure the test discovery is working.
115+
/// </summary>
116+
[Fact]
117+
public void TestFixturesDirectory_Exists()
118+
{
119+
Output.WriteLine($"Looking for fixtures in: {FixturesPath}");
120+
Assert.True(Directory.Exists(FixturesPath),
121+
$"TestFixtures directory not found at: {FixturesPath}");
122+
}
123+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Test Fixtures
2+
3+
This directory contains file-based integration tests for the Sharpy compiler.
4+
5+
## Structure
6+
7+
```
8+
TestFixtures/
9+
├── basics/ # Basic language features
10+
│ ├── hello_world.spy
11+
│ ├── hello_world.expected
12+
│ ├── arithmetic.spy
13+
│ └── arithmetic.expected
14+
├── functions/ # Function-related tests
15+
├── control_flow/ # Control flow tests (if/elif/else, loops)
16+
├── errors/ # Tests that expect compilation errors
17+
└── README.md
18+
```
19+
20+
## Adding a New Test
21+
22+
### Success Test (compilation should succeed)
23+
24+
1. Create a `.spy` file with your Sharpy source code
25+
2. Create a matching `.expected` file with the exact expected stdout output
26+
27+
Example:
28+
```
29+
my_feature/
30+
├── test_name.spy # Your Sharpy code
31+
└── test_name.expected # Expected stdout output (including newlines)
32+
```
33+
34+
### Error Test (compilation should fail)
35+
36+
1. Create a `.spy` file with Sharpy code that should fail to compile
37+
2. Create a matching `.error` file with a substring that should appear in the error message
38+
39+
Example:
40+
```
41+
errors/
42+
├── undefined_var.spy # Code with an undefined variable
43+
└── undefined_var.error # Contains: "undefined_var" (substring to match)
44+
```
45+
46+
## Running Tests
47+
48+
```bash
49+
# Run all file-based tests
50+
dotnet test --filter "FullyQualifiedName~FileBasedIntegrationTests"
51+
52+
# Run a specific test (by name)
53+
dotnet test --filter "FullyQualifiedName~FileBasedIntegrationTests&DisplayName~hello_world"
54+
```
55+
56+
## Converting Dogfood Outputs
57+
58+
To convert a successful dogfood output to a test fixture:
59+
60+
1. Copy the `source.spy` file to an appropriate subdirectory
61+
2. Rename it to a descriptive name (e.g., `abstract_class_basic.spy`)
62+
3. Create the matching `.expected` file with the expected output
63+
4. Run the test to verify
64+
65+
## Notes
66+
67+
- Expected output files should match stdout exactly, including trailing newlines
68+
- Error files use case-insensitive substring matching
69+
- Tests are discovered automatically at runtime - no need to register them
70+
- Test names in the runner are based on the file path relative to TestFixtures/
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
15
2+
5
3+
50
4+
2
5+
0
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
x: int = 10
2+
y: int = 5
3+
4+
print(x + y)
5+
print(x - y)
6+
print(x * y)
7+
print(x // y)
8+
print(x % y)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello, World!
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
print("Hello, World!")
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
42
2+
3.14
3+
hello
4+
True
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
x = 42
2+
y = 3.14
3+
z = "hello"
4+
flag = True
5+
6+
print(x)
7+
print(y)
8+
print(z)
9+
print(flag)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
0
2+
1
3+
2
4+
3
5+
4

0 commit comments

Comments
 (0)