diff --git a/Sources/sonyalib/Tests/sonyalibTests/sonyalibTests.swift b/Sources/sonyalib/Tests/sonyalibTests/sonyalibTests.swift index d4c3b4d..a8c72c6 100644 --- a/Sources/sonyalib/Tests/sonyalibTests/sonyalibTests.swift +++ b/Sources/sonyalib/Tests/sonyalibTests/sonyalibTests.swift @@ -42,51 +42,102 @@ import Testing #expect(tokens == [.recipe("build"), .command("swift build")]) } -// MARK: - Parser - -@Test func parser_single_recipe_with_command() { - let tokens: [Token] = [.recipe("build"), .command("swift build")] - let ast = Parser().parse(tokens) - #expect(ast != nil) - #expect(ast?.rules["build"]?.commands == ["swift build"]) +@Test func lexer_unrecognized_line_produces_error_token() { + let tokens = Lexer().tokenize("???bad line") + #expect(tokens == [.error(line: "???bad line")]) } -@Test func parser_recipe_with_dependency() { +@Test func lexer_multiple_errors_in_multiline() { + let input = """ + build: + ???bad + !!!also bad + """ + let tokens = Lexer().tokenize(input) + #expect(tokens.contains(.error(line: "???bad"))) + #expect(tokens.contains(.error(line: "!!!also bad"))) +} + +// MARK: - Parser + +@Test func parser_single_recipe_with_command() throws { + let tokens: [Token] = [.recipe("build"), .command("swift build")] + let ast = try Parser().parse(tokens).get() + #expect(ast.rules["build"]?.commands == ["swift build"]) +} + +@Test func parser_recipe_with_dependency() throws { let tokens: [Token] = [ .recipe("test"), .dependency("build"), .command("swift test") ] - let ast = Parser().parse(tokens) - #expect(ast?.rules["test"]?.dependencies == ["build"]) - #expect(ast?.rules["test"]?.commands == ["swift test"]) + let ast = try Parser().parse(tokens).get() + #expect(ast.rules["test"]?.dependencies == ["build"]) + #expect(ast.rules["test"]?.commands == ["swift test"]) } -@Test func parser_variable_is_stored() { +@Test func parser_variable_is_stored() throws { let tokens: [Token] = [.variable(name: "CC", value: "clang")] - let ast = Parser().parse(tokens) - #expect(ast?.vars["CC"] == "clang") + let ast = try Parser().parse(tokens).get() + #expect(ast.vars["CC"] == "clang") } -@Test func parser_multiple_recipes() { +@Test func parser_multiple_recipes() throws { let tokens: [Token] = [ .recipe("build"), .command("swift build"), .newline, .recipe("test"), .command("swift test"), ] - let ast = Parser().parse(tokens) - #expect(ast?.rules["build"]?.commands == ["swift build"]) - #expect(ast?.rules["test"]?.commands == ["swift test"]) + let ast = try Parser().parse(tokens).get() + #expect(ast.rules["build"]?.commands == ["swift build"]) + #expect(ast.rules["test"]?.commands == ["swift test"]) } -@Test func parser_variable_before_recipe_flushes_context() { - // A variable declaration after a recipe must flush the current recipe first. +@Test func parser_variable_before_recipe_flushes_context() throws { let tokens: [Token] = [ .recipe("build"), .command("swift build"), .variable(name: "X", value: "1"), ] - let ast = Parser().parse(tokens) - #expect(ast?.rules["build"]?.commands == ["swift build"]) - #expect(ast?.vars["X"] == "1") + let ast = try Parser().parse(tokens).get() + #expect(ast.rules["build"]?.commands == ["swift build"]) + #expect(ast.vars["X"] == "1") +} + +// MARK: - Parser error handling + +@Test func parser_single_error_token_returns_failure() { + let tokens: [Token] = [.error(line: "???bad")] + let result = Parser().parse(tokens) + guard case .failure(let err) = result else { + Issue.record("expected failure, got success") + return + } + #expect(err.errorLines == ["???bad"]) +} + +@Test func parser_multiple_error_tokens_collected() { + let tokens: [Token] = [ + .error(line: "???bad"), + .recipe("build"), .command("swift build"), + .error(line: "!!!also bad"), + ] + let result = Parser().parse(tokens) + guard case .failure(let err) = result else { + Issue.record("expected failure, got success") + return + } + #expect(err.errorLines.count == 2) + #expect(err.errorLines.contains("???bad")) + #expect(err.errorLines.contains("!!!also bad")) +} + +@Test func parser_no_error_tokens_returns_success() { + let tokens: [Token] = [.recipe("build"), .command("swift build")] + let result = Parser().parse(tokens) + guard case .success = result else { + Issue.record("expected success, got failure") + return + } } // MARK: - Variable resolution