diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-26 09:54:27 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-26 09:54:27 +0200 |
| commit | cb4c8f8055a7145463928b0d55e1f20e0dae0024 (patch) | |
| tree | 03c78131b3b0cdba7997bd9ad33789e59488c5c5 /integrationtests | |
| parent | 09bfecf655ed06ca1ed34e972781fd97775db840 (diff) | |
refactor: move integration tests to ./integrationtests folder
Integration tests that use the gt tool directly via exec.Command
have been moved from cmd/gt/cli_test.go to integrationtests/cli_test.go.
Unit tests in cmd/gt/main_test.go remain in place as they test
internal functions (runCommand) rather than the binary.
Package declaration changed from 'main' to 'integrationtests'.
Diffstat (limited to 'integrationtests')
| -rw-r--r-- | integrationtests/cli_test.go | 546 |
1 files changed, 546 insertions, 0 deletions
diff --git a/integrationtests/cli_test.go b/integrationtests/cli_test.go new file mode 100644 index 0000000..8a0a076 --- /dev/null +++ b/integrationtests/cli_test.go @@ -0,0 +1,546 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2026 Paul Buetow + +package integrationtests + +import ( + "os" + "os/exec" + "strings" + "testing" +) + +// buildBinary builds the gt binary to a temporary location. +func buildBinary(t *testing.T) string { + t.Helper() + + // Get the project root directory + projectRoot := os.Getenv("GITHUB_WORKSPACE") + if projectRoot == "" { + projectRoot = "/home/paul/git/gt" + } + + buildCmd := exec.Command("go", "build", "-o", "/tmp/gt-test", "./cmd/gt") + buildCmd.Dir = projectRoot + if err := buildCmd.Run(); err != nil { + t.Fatalf("build failed: %v", err) + } + + t.Cleanup(func() { + // Explicitly ignore error return from os.Remove during cleanup + _ = os.Remove("/tmp/gt-test") + }) + + return "/tmp/gt-test" +} + +// TestCLIVersion tests that the version command works correctly. +func TestCLIVersion(t *testing.T) { + binaryPath := buildBinary(t) + + cmd := exec.Command(binaryPath, "version") + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("version command failed: %v\nOutput: %s", err, string(output)) + } + + versionOutput := strings.TrimSpace(string(output)) + if !strings.HasPrefix(versionOutput, "v") { + t.Errorf("version output should start with 'v', got: %s", versionOutput) + } +} + +// TestCLIVersionOnly tests that the version command works correctly. +func TestCLIVersionOnly(t *testing.T) { + binaryPath := buildBinary(t) + + cmd := exec.Command(binaryPath, "version") + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("version command failed: %v\nOutput: %s", err, string(output)) + } + + versionOutput := strings.TrimSpace(string(output)) + if !strings.HasPrefix(versionOutput, "v") { + t.Errorf("version output should start with 'v', got: %s", versionOutput) + } +} + +// TestCLIPercentageCalculation tests percentage calculation commands. + +// TestCLIPercentageCalculation tests percentage calculation commands. +func TestCLIPercentageCalculation(t *testing.T) { + binaryPath := buildBinary(t) + + tests := []struct { + name string + args []string + expected string + }{ + { + name: "20% of 150", + args: []string{"20% of 150"}, + expected: "30", + }, + { + name: "what is 20% of 150", + args: []string{"what is 20% of 150"}, + expected: "30", + }, + { + name: "30 is what % of 150", + args: []string{"30 is what % of 150"}, + expected: "20", + }, + { + name: "30 is 20% of what", + args: []string{"30 is 20% of what"}, + expected: "150", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := exec.Command(binaryPath, tt.args...) + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("command failed: %v\nOutput: %s", err, string(output)) + } + + outputStr := strings.TrimSpace(string(output)) + if !strings.Contains(outputStr, tt.expected) { + t.Errorf("output should contain '%s', got: %s", tt.expected, outputStr) + } + }) + } +} + +// TestCLIRPNCalculation tests RPN calculation commands. +func TestCLIRPNCalculation(t *testing.T) { + binaryPath := buildBinary(t) + + tests := []struct { + name string + args []string + expected string + }{ + { + name: "3 4 +", + args: []string{"3 4 +"}, + expected: "7", + }, + { + name: "5 6 *", + args: []string{"5 6 *"}, + expected: "30", + }, + { + name: "10 2 /", + args: []string{"10 2 /"}, + expected: "5", + }, + { + name: "2 3 ^", + args: []string{"2 3 ^"}, + expected: "8", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := exec.Command(binaryPath, tt.args...) + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("command failed: %v\nOutput: %s", err, string(output)) + } + + outputStr := strings.TrimSpace(string(output)) + if !strings.Contains(outputStr, tt.expected) { + t.Errorf("output should contain '%s', got: %s", tt.expected, outputStr) + } + }) + } +} + +// TestCLIExitCode tests that the CLI returns proper exit codes. +func TestCLIExitCode(t *testing.T) { + binaryPath := buildBinary(t) + + // Test successful command returns 0 + cmd := exec.Command(binaryPath, "version") + if err := cmd.Run(); err != nil { + t.Errorf("version command should succeed, got error: %v", err) + } + + // Test invalid command returns non-zero + cmd = exec.Command(binaryPath, "invalidcommand") + output, err := cmd.CombinedOutput() + if err == nil { + t.Errorf("invalid command should fail, but succeeded") + } + if len(output) > 0 && !strings.Contains(string(output), "Error:") { + t.Errorf("error output should contain 'Error:', got: %s", string(output)) + } +} + +// TestCLIInvalidPercentage tests invalid percentage calculation. +func TestCLIInvalidPercentage(t *testing.T) { + binaryPath := buildBinary(t) + + cmd := exec.Command(binaryPath, "invalid percentage") + output, err := cmd.CombinedOutput() + if err == nil { + t.Errorf("invalid percentage should fail, but succeeded") + } + if len(output) > 0 && !strings.Contains(string(output), "Error:") { + t.Errorf("error output should contain 'Error:', got: %s", string(output)) + } +} + +// TestCLIInvalidRPN tests invalid RPN expression. +func TestCLIInvalidRPN(t *testing.T) { + binaryPath := buildBinary(t) + + cmd := exec.Command(binaryPath, "3 +") + output, err := cmd.CombinedOutput() + if err == nil { + t.Errorf("invalid RPN should fail, but succeeded") + } + if len(output) > 0 && !strings.Contains(string(output), "Error:") { + t.Errorf("error output should contain 'Error:', got: %s", string(output)) + } +} + +// TestCLIStdin tests that stdin input works correctly. +func TestCLIStdin(t *testing.T) { + binaryPath := buildBinary(t) + + tests := []struct { + name string + stdin string + expected string + }{ + { + name: "RPN expression via stdin", + stdin: "3 4 +", + expected: "7", + }, + { + name: "Percentage expression via stdin", + stdin: "20% of 150", + expected: "30", + }, + { + name: "Variable assignment via stdin", + stdin: "x 5 = x x +", + expected: "10", + }, + { + name: "Boolean comparison via stdin", + stdin: "5 3 ==", + expected: "false", + }, + { + name: "Boolean to number coercion via stdin", + stdin: "true 2 *", + expected: "2", + }, + { + name: "Complex expression via stdin", + stdin: "2 3 + 4 *", + expected: "20", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := exec.Command(binaryPath) + cmd.Stdin = strings.NewReader(tt.stdin) + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("command failed: %v\nOutput: %s", err, string(output)) + } + + outputStr := strings.TrimSpace(string(output)) + if !strings.Contains(outputStr, tt.expected) { + t.Errorf("output should contain '%s', got: %s", tt.expected, outputStr) + } + }) + } +} + +// TestCLIStdinWithPiping tests stdin via pipe (simulating `echo EXP | gt`). +func TestCLIStdinWithPiping(t *testing.T) { + binaryPath := buildBinary(t) + + tests := []struct { + name string + input string + expected string + }{ + { + name: "echo 3 4 +", + input: "3 4 +", + expected: "7", + }, + { + name: "echo 20%% of 150", + input: "20% of 150", + expected: "30", + }, + { + name: "echo x 5 = x x +", + input: "x 5 = x x +", + expected: "10", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Simulate piping: echo INPUT | gt + cmd := exec.Command(binaryPath) + cmd.Stdin = strings.NewReader(tt.input) + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("command failed: %v\nOutput: %s", err, string(output)) + } + + outputStr := strings.TrimSpace(string(output)) + if !strings.Contains(outputStr, tt.expected) { + t.Errorf("output should contain '%s', got: %s", tt.expected, outputStr) + } + }) + } +} + +// TestCLIStdinWithMultipleLines tests stdin with multiple lines (should use first line). +func TestCLIStdinWithMultipleLines(t *testing.T) { + binaryPath := buildBinary(t) + + tests := []struct { + name string + stdin string + expected string + }{ + { + name: "Multiple lines - first line used", + stdin: "3 4 +\n5 6 +", + expected: "7", + }, + { + name: "Empty line then expression", + stdin: "\n3 4 +", + expected: "7", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := exec.Command(binaryPath) + cmd.Stdin = strings.NewReader(tt.stdin) + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("command failed: %v\nOutput: %s", err, string(output)) + } + + outputStr := strings.TrimSpace(string(output)) + if !strings.Contains(outputStr, tt.expected) { + t.Errorf("output should contain '%s', got: %s", tt.expected, outputStr) + } + }) + } +} + +// TestCLIVariableAssignment tests all variable assignment syntaxes. +func TestCLIVariableAssignment(t *testing.T) { + binaryPath := buildBinary(t) + + tests := []struct { + name string + args []string + expected string + }{ + { + name: "Standard assignment x = 2", + args: []string{"x", "2", "=", "x", "2", "+"}, + expected: "4", + }, + { + name: "Right assignment x 2 := (value on stack, right)", + args: []string{"x", "2", ":=", "x", "2", "+"}, + expected: "4", + }, + { + name: "Left assignment 2 x =: (value on stack, left)", + args: []string{"2", "x", "=: ", "x", "2", "+"}, + expected: "4", + }, + { + name: "Stack variant with =: (value on stack, left)", + args: []string{"2", "x", "=: ", "x", "2", "+"}, + expected: "4", + }, + { + name: "Assignment with existing variable", + args: []string{"x", "5", "=", "x", "3", "+"}, + expected: "8", + }, + { + name: "Assignment with complex expression", + args: []string{"x", "2", "=", "x", "x", "*", "x", "+"}, + expected: "6", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := exec.Command(binaryPath, tt.args...) + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("command failed: %v\nOutput: %s", err, string(output)) + } + + outputStr := strings.TrimSpace(string(output)) + if !strings.Contains(outputStr, tt.expected) { + t.Errorf("output should contain '%s', got: %s", tt.expected, outputStr) + } + }) + } +} + +// TestCLIVariableAssignmentSyntaxes tests each assignment syntax individually +// and verifies the variable can be used in subsequent expressions. +func TestCLIVariableAssignmentSyntaxes(t *testing.T) { + binaryPath := buildBinary(t) + + tests := []struct { + name string + args []string + expected string + }{ + { + name: "Assignment: x = 2, then x 2 +", + args: []string{"x", "2", "=", "x", "2", "+"}, + expected: "4", + }, + { + name: "Right assignment: x 2 :=, then x 2 +", + args: []string{"x", "2", ":=", "x", "2", "+"}, + expected: "4", + }, + { + name: "Left assignment: 2 x =:, then x 2 +", + args: []string{"2", "x", "=: ", "x", "2", "+"}, + expected: "4", + }, + { + name: "Assignment: y = 10, then y 5 *", + args: []string{"y", "10", "=", "y", "5", "*"}, + expected: "50", + }, + { + name: "Assignment: pi = 3.14159, then pi 2 *", + args: []string{"pi", "3.14159", "=", "pi", "2", "*"}, + expected: "6.28318", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := exec.Command(binaryPath, tt.args...) + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("command failed: %v\nOutput: %s", err, string(output)) + } + + outputStr := strings.TrimSpace(string(output)) + if !strings.Contains(outputStr, tt.expected) { + t.Errorf("output should contain '%s', got: %s", tt.expected, outputStr) + } + }) + } +} + +// TestCLIVariableAssignmentPreservation tests that variables persist within a single expression. +func TestCLIVariableAssignmentPreservation(t *testing.T) { + binaryPath := buildBinary(t) + + tests := []struct { + name string + args []string + expected string + }{ + { + name: "Single assignment, multiple uses", + args: []string{"x", "5", "=", "x", "x", "+"}, + expected: "10", // x=5, then x+x=10 + }, + { + name: "Assignment with calculation as value (stack-variant)", + args: []string{"2", "3", "+", "x", "=: ", "x", "4", "*"}, + expected: "20", // 2+3=5, x=: assigns 5 to x, x*4=20 + }, + { + name: "Stack-variant with := (value on stack)", + args: []string{"x", "2", "3", "+", ":=", "x", "1", "+"}, + expected: "6", // x=2+3=5, x+1=6 + }, + { + name: "Stack-variant with =: (value first)", + args: []string{"2", "3", "+", "x", "=: ", "x", "1", "+"}, + expected: "6", // 2+3=5, x=: assigns 5 to x, x+1=6 + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := exec.Command(binaryPath, tt.args...) + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("command failed: %v\nOutput: %s", err, string(output)) + } + + outputStr := strings.TrimSpace(string(output)) + if !strings.Contains(outputStr, tt.expected) { + t.Errorf("output should contain '%s', got: %s", tt.expected, outputStr) + } + }) + } +} + +// TestCLIVariableAssignmentRepetition tests that repeated assignment works correctly. +func TestCLIVariableAssignmentRepetition(t *testing.T) { + binaryPath := buildBinary(t) + + tests := []struct { + name string + args []string + expected string + }{ + { + name: "Reassign same variable", + args: []string{"x", "5", ":=", "x", "10", ":=", "x", "2", "+"}, + expected: "12", // x=5, x=10, x+2=12 + }, + { + name: "Stack-variant reassignment", + args: []string{"x", "1", ":=", "x", "2", ":=", "x", "3", "+"}, + expected: "5", // x=1 then x=2, x+3=5 + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := exec.Command(binaryPath, tt.args...) + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("command failed: %v\nOutput: %s", err, string(output)) + } + + outputStr := strings.TrimSpace(string(output)) + if !strings.Contains(outputStr, tt.expected) { + t.Errorf("output should contain '%s', got: %s", tt.expected, outputStr) + } + }) + } +} |
