diff options
| -rw-r--r-- | cmd/perc/main_test.go | 155 | ||||
| -rw-r--r-- | internal/rpn/rpn_test.go | 107 |
2 files changed, 260 insertions, 2 deletions
diff --git a/cmd/perc/main_test.go b/cmd/perc/main_test.go new file mode 100644 index 0000000..4a7023b --- /dev/null +++ b/cmd/perc/main_test.go @@ -0,0 +1,155 @@ +package main + +import ( + "os" + "strings" + "testing" +) + +func TestRunCommandVersion(t *testing.T) { + args := []string{"perc", "version"} + result, err := runCommand(args) + if err != nil { + t.Fatalf("runCommand(['perc', 'version']) returned error: %v", err) + } + if result != "dev" && !strings.HasPrefix(result, "v") { + t.Errorf("runCommand(['perc', 'version']) = %q, expected version string", result) + } +} + +func TestRunCommandCalc(t *testing.T) { + args := []string{"perc", "calc", "3", "4", "+"} + result, err := runCommand(args) + if err != nil { + t.Fatalf("runCommand(['perc', 'calc', '3', '4', '+']) returned error: %v", err) + } + if result != "7" { + t.Errorf("runCommand(['perc', 'calc', '3', '4', '+']) = %q, want '7'", result) + } +} + +func TestRunCommandRPN(t *testing.T) { + args := []string{"perc", "rpn", "3", "4", "+"} + result, err := runCommand(args) + if err != nil { + t.Fatalf("runCommand(['perc', 'rpn', '3', '4', '+']) returned error: %v", err) + } + if result != "7" { + t.Errorf("runCommand(['perc', 'rpn', '3', '4', '+']) = %q, want '7'", result) + } +} + +func TestRunCommandRPNWithAssignment(t *testing.T) { + args := []string{"perc", "rpn", "x", "5", "=", "x", "x", "+"} + result, err := runCommand(args) + if err != nil { + t.Fatalf("runCommand with assignment returned error: %v", err) + } + if result != "10" { + t.Errorf("runCommand with assignment = %q, want '10'", result) + } +} + +func TestRunCommandPercentage(t *testing.T) { + args := []string{"perc", "20% of 150"} + result, err := runCommand(args) + if err != nil { + t.Fatalf("runCommand(['perc', '20%% of 150']) returned error: %v", err) + } + if !strings.Contains(result, "30") { + t.Errorf("runCommand(['perc', '20%% of 150']) = %q, should contain '30'", result) + } +} + +func TestRunCommandMissingExpression(t *testing.T) { + args := []string{"perc", "calc"} + _, err := runCommand(args) + if err == nil { + t.Error("runCommand(['perc', 'calc']) should return error for missing expression") + } + if !strings.Contains(err.Error(), "missing expression") { + t.Errorf("Error = %v, should contain 'missing expression'", err) + } +} + +func TestRunCommandInvalidRPN(t *testing.T) { + args := []string{"perc", "rpn", "5", "0", "/"} + _, err := runCommand(args) + if err == nil { + t.Error("runCommand(['perc', 'rpn', '5', '0', '/']) should return error for division by zero") + } +} + +func TestRunCommandUnknownToken(t *testing.T) { + args := []string{"perc", "rpn", "unknown +"} + _, err := runCommand(args) + if err == nil { + t.Error("runCommand(['perc', 'rpn', 'unknown +']) should return error") + } +} + +func TestPrintUsage(t *testing.T) { + // Just verify the function doesn't panic + // We can't easily test the output since it goes to stdout + printUsage() +} + +func TestRunCommandUnknownSubcommand(t *testing.T) { + args := []string{"perc", "unknown", "3", "4", "+"} + // This will fall through to calculator.Parse which will fail + _, err := runCommand(args) + if err == nil { + t.Error("runCommand with unknown subcommand should return error") + } +} + +func TestMain(t *testing.T) { + // Save original os.Args + oldArgs := os.Args + defer func() { os.Args = oldArgs }() + + // Test with version command + os.Args = []string{"perc", "version"} + // Note: we can't actually call main() in tests because it calls os.Exit() + // Instead we test via runCommand which is what main() calls + result, err := runCommand(os.Args) + if err != nil { + t.Fatalf("runCommand(['perc', 'version']) returned error: %v", err) + } + if result != "dev" && !strings.HasPrefix(result, "v") { + t.Errorf("runCommand(['perc', 'version']) = %q, expected version string", result) + } +} + +func TestRunCommandCalcChain(t *testing.T) { + args := []string{"perc", "calc", "3", "4", "+", "4", "4", "-", "*"} + result, err := runCommand(args) + if err != nil { + t.Fatalf("runCommand with chain returned error: %v", err) + } + if result != "0" { + t.Errorf("runCommand with chain = %q, want '0'", result) + } +} + +func TestRunCommandRPNPower(t *testing.T) { + args := []string{"perc", "rpn", "2", "3", "^"} + result, err := runCommand(args) + if err != nil { + t.Fatalf("runCommand with power returned error: %v", err) + } + if result != "8" { + t.Errorf("runCommand with power = %q, want '8'", result) + } +} + +func TestRunCommandRPNModulo(t *testing.T) { + args := []string{"perc", "rpn", "10", "3", "%"} + result, err := runCommand(args) + if err != nil { + t.Fatalf("runCommand with modulo returned error: %v", err) + } + if result != "1" { + t.Errorf("runCommand with modulo = %q, want '1'", result) + } +} diff --git a/internal/rpn/rpn_test.go b/internal/rpn/rpn_test.go index e35620a..46590b8 100644 --- a/internal/rpn/rpn_test.go +++ b/internal/rpn/rpn_test.go @@ -523,12 +523,17 @@ func TestParseAndEvaluateEvaluateErrors(t *testing.T) { { name: "'d' command not supported", input: "d", - expectedError: "'d' command not supported", + expectedError: "'d' command not supported as standalone token", }, { name: "empty result after evaluation", input: "1 2 + pop", // 1 2 + => 3, then pop => empty stack - expectedError: "empty result", + expectedError: "empty result: expression evaluated to nothing", + }, + { + name: "stack overflow (simulate many numbers)", + input: "", // placeholder + expectedError: "stack overflow", }, } @@ -550,3 +555,101 @@ func TestParseAndEvaluateEvaluateErrors(t *testing.T) { }) } } + +func TestResultStackErrors(t *testing.T) { + v := NewVariables().(*Variables) + r := NewRPN(v) + + // Test error cases in ResultStack function + tests := []struct { + name string + input []string + expectedError string + }{ + { + name: "division by zero", + input: []string{"5", "0", "/"}, + expectedError: "division by zero", + }, + { + name: "unknown token", + input: []string{"1", "2", "+", "unknown"}, + expectedError: "unknown token", + }, + { + name: "insufficient operands for +", + input: []string{"5", "+"}, + expectedError: "insufficient operands", + }, + { + name: "insufficient operands for -", + input: []string{"5", "-"}, + expectedError: "insufficient operands", + }, + { + name: "invalid assignment syntax in ResultStack", + input: []string{"="}, + expectedError: "unknown token '='", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := r.ResultStack(tt.input) + if err == nil { + t.Errorf("ResultStack(%v) expected error, got nil", tt.input) + return + } + if !strings.Contains(err.Error(), tt.expectedError) { + t.Errorf("ResultStack(%v) error = %q, want to contain %q", tt.input, err.Error(), tt.expectedError) + } + }) + } +} + +func TestResultStackMultipleValues(t *testing.T) { + v := NewVariables().(*Variables) + r := NewRPN(v) + + // Test Case where stack has multiple values at the end + tests := []struct { + name string + input []string + expected string + }{ + { + name: "two values on stack", + input: []string{"1", "2", "3", "+"}, // 1 2 3 + => 1 (5) => two values: 1, 5 + expected: "1 5", // Show should show all values + }, + { + name: "three values on stack", + input: []string{"1", "2", "3", "4"}, // just push 4 numbers + expected: "1 2 3 4", + }, + { + name: "multiple values with variables", + input: []string{"x", "y", "z"}, // after setting variables + expected: "10 20 30", // variables x, y, z have values 10, 20, 30 + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Set up variables if needed + if tt.name == "multiple values with variables" { + r.ParseAndEvaluate("x = 10") + r.ParseAndEvaluate("y = 20") + r.ParseAndEvaluate("z = 30") + } + + result, err := r.ResultStack(tt.input) + if err != nil { + t.Fatalf("ResultStack(%v) returned error: %v", tt.input, err) + } + if result != tt.expected { + t.Errorf("ResultStack(%v) = %q, want %q", tt.input, result, tt.expected) + } + }) + } +} |
