summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/perc/main_test.go155
-rw-r--r--internal/rpn/rpn_test.go107
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)
+ }
+ })
+ }
+}