import Testing @testable import sonyalib // MARK: - Lexer @Test func lexer_tokenizes_recipe_with_no_deps() { let tokens = Lexer().tokenize("build:") #expect(tokens == [.recipe("build")]) } @Test func lexer_tokenizes_recipe_with_deps() { let tokens = Lexer().tokenize("test: build lint") #expect(tokens == [.recipe("test"), .dependency("build"), .dependency("lint")]) } @Test func lexer_tokenizes_command() { let tokens = Lexer().tokenize(" echo hello") #expect(tokens == [.command("echo hello")]) } @Test func lexer_tokenizes_variable() { let tokens = Lexer().tokenize("CC = clang") #expect(tokens == [.variable(name: "CC", value: "clang")]) } @Test func lexer_tokenizes_comment() { let tokens = Lexer().tokenize("# this is a comment") #expect(tokens == [.comment(" this is a comment")]) } @Test func lexer_empty_line_produces_newline() { let tokens = Lexer().tokenize("") #expect(tokens == [.newline]) } @Test func lexer_multiline_sonyafile() { let input = """ build: swift build """ let tokens = Lexer().tokenize(input) #expect(tokens == [.recipe("build"), .command("swift build")]) } @Test func lexer_unrecognized_line_produces_error_token() { let tokens = Lexer().tokenize("???bad line") #expect(tokens == [.error(line: "???bad line")]) } @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 = try Parser().parse(tokens).get() #expect(ast.rules["test"]?.dependencies == ["build"]) #expect(ast.rules["test"]?.commands == ["swift test"]) } @Test func parser_variable_is_stored() throws { let tokens: [Token] = [.variable(name: "CC", value: "clang")] let ast = try Parser().parse(tokens).get() #expect(ast.vars["CC"] == "clang") } @Test func parser_multiple_recipes() throws { let tokens: [Token] = [ .recipe("build"), .command("swift build"), .newline, .recipe("test"), .command("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() throws { let tokens: [Token] = [ .recipe("build"), .command("swift build"), .variable(name: "X", value: "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 @Test func resolve_substitutes_known_variable() { let result = resolve("$(CC) main.c", vars: ["CC": "clang"]) #expect(result == "clang main.c") } @Test func resolve_returns_nil_for_unknown_variable() { let result = resolve("$(MISSING) main.c", vars: [:]) #expect(result == nil) } @Test func resolve_no_variables_passes_through() { let result = resolve("echo hello", vars: [:]) #expect(result == "echo hello") } @Test func resolve_multiple_substitutions() { let result = resolve("$(CC) $(FLAGS) main.c", vars: ["CC": "clang", "FLAGS": "-O2"]) #expect(result == "clang -O2 main.c") }