summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-20 22:32:55 +0200
committerPaul Buetow <paul@buetow.org>2026-03-20 22:32:55 +0200
commit4636c649d74b5a620d70a32438869b2450daddb7 (patch)
tree34551ad4e61f46b627450ce9af20e7338049e853 /internal
parent7df37824bb6408fb137eb2eb22bc93563ed37535 (diff)
internal/rpn: fix race condition in concurrent test
- Each goroutine in TestOperationsConcurrent now uses its own stack - This prevents data races when accessing Stack from multiple goroutines
Diffstat (limited to 'internal')
-rw-r--r--internal/rpn/operations_test.go3
-rw-r--r--internal/rpn/rpn.go10
-rw-r--r--internal/rpn/rpn_test.go58
-rw-r--r--internal/rpn/variables.go6
4 files changed, 39 insertions, 38 deletions
diff --git a/internal/rpn/operations_test.go b/internal/rpn/operations_test.go
index 0083196..7aa6a32 100644
--- a/internal/rpn/operations_test.go
+++ b/internal/rpn/operations_test.go
@@ -525,13 +525,14 @@ func TestOperationsClearVariables(t *testing.T) {
func TestOperationsConcurrent(t *testing.T) {
v := NewVariables().(*Variables)
o := NewOperations(v)
- s := NewStack()
// Test concurrent variable access
+ // Each goroutine uses its own stack to avoid race conditions
done := make(chan bool, 10)
for i := 0; i < 5; i++ {
go func(id int) {
name := fmt.Sprintf("concurrent%d", id)
+ s := NewStack()
s.Push(float64(id))
o.AssignVariable(s, name)
done <- true
diff --git a/internal/rpn/rpn.go b/internal/rpn/rpn.go
index 5fd1d39..debc2c8 100644
--- a/internal/rpn/rpn.go
+++ b/internal/rpn/rpn.go
@@ -8,9 +8,9 @@ import (
// RPN represents the RPN parser and evaluator.
type RPN struct {
- vars VariableStore
- ops Operator
- maxStack int
+ vars VariableStore
+ ops Operator
+ maxStack int
currentStack *Stack
}
@@ -85,8 +85,8 @@ func (r *RPN) handleAssignment(input string) (string, bool, error) {
// Handle assignment with expression: "name value = expression..."
pos := strings.Index(input, " = ")
if pos >= 0 {
- before := input[:pos] // "name value"
- after := input[pos+3:] // "expr..."
+ before := input[:pos] // "name value"
+ after := input[pos+3:] // "expr..."
beforeFields := strings.Fields(before)
if len(beforeFields) == 2 {
diff --git a/internal/rpn/rpn_test.go b/internal/rpn/rpn_test.go
index 5cd0af4..d68d43a 100644
--- a/internal/rpn/rpn_test.go
+++ b/internal/rpn/rpn_test.go
@@ -462,23 +462,23 @@ func TestParseAndEvaluateAssignmentErrors(t *testing.T) {
expectedError string
}{
{
- name: "invalid value for assignment (non-numeric)",
- input: "x abc =",
+ name: "invalid value for assignment (non-numeric)",
+ input: "x abc =",
expectedError: "unknown token 'x'",
},
{
- name: "assignment with variable name containing space",
- input: "my var 5 =",
+ name: "assignment with variable name containing space",
+ input: "my var 5 =",
expectedError: "unknown token 'my'",
},
{
- name: "assignment with value containing space",
- input: "x 5 6 =",
+ name: "assignment with value containing space",
+ input: "x 5 6 =",
expectedError: "unknown token 'x'",
},
{
- name: "empty assignment",
- input: " = ",
+ name: "empty assignment",
+ input: " = ",
expectedError: "invalid assignment syntax",
},
}
@@ -507,23 +507,23 @@ func TestParseAndEvaluateEvaluateErrors(t *testing.T) {
expectedError string
}{
{
- name: "invalid assignment syntax (standalone =)",
- input: "=",
+ name: "invalid assignment syntax (standalone =)",
+ input: "=",
expectedError: "invalid assignment syntax",
},
{
- name: "'d' command not supported",
- input: "d",
+ name: "'d' command not supported",
+ input: "d",
expectedError: "'d' command not supported as standalone token",
},
{
- name: "empty result after evaluation",
- input: "1 2 + pop", // 1 2 + => 3, then pop => empty stack
+ name: "empty result after evaluation",
+ input: "1 2 + pop", // 1 2 + => 3, then pop => empty stack
expectedError: "empty result: expression evaluated to nothing",
},
{
- name: "stack overflow (simulate many numbers)",
- input: "", // placeholder
+ name: "stack overflow (simulate many numbers)",
+ input: "", // placeholder
expectedError: "stack overflow",
},
}
@@ -560,28 +560,28 @@ func TestResultStackErrors(t *testing.T) {
expectedError string
}{
{
- name: "division by zero",
- input: []string{"5", "0", "/"},
+ name: "division by zero",
+ input: []string{"5", "0", "/"},
expectedError: "division by zero",
},
{
- name: "unknown token",
- input: []string{"1", "2", "+", "unknown"},
+ name: "unknown token",
+ input: []string{"1", "2", "+", "unknown"},
expectedError: "unknown token",
},
{
- name: "insufficient operands for +",
- input: []string{"5", "+"},
+ name: "insufficient operands for +",
+ input: []string{"5", "+"},
expectedError: "insufficient operands",
},
{
- name: "insufficient operands for -",
- input: []string{"5", "-"},
+ name: "insufficient operands for -",
+ input: []string{"5", "-"},
expectedError: "insufficient operands",
},
{
- name: "invalid assignment syntax in ResultStack",
- input: []string{"="},
+ name: "invalid assignment syntax in ResultStack",
+ input: []string{"="},
expectedError: "unknown token '='",
},
}
@@ -613,7 +613,7 @@ func TestResultStackMultipleValues(t *testing.T) {
{
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
+ expected: "1 5", // Show should show all values
},
{
name: "three values on stack",
@@ -623,7 +623,7 @@ func TestResultStackMultipleValues(t *testing.T) {
{
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
+ expected: "10 20 30", // variables x, y, z have values 10, 20, 30
},
}
@@ -913,7 +913,7 @@ func TestHyperModuloByZero(t *testing.T) {
func TestHyperOperatorEdgeCases(t *testing.T) {
// Test with single value should error for all hyper operators
testCases := []struct {
- input string
+ input string
operands int
}{
{"100 [%]", 1},
diff --git a/internal/rpn/variables.go b/internal/rpn/variables.go
index 46efa2c..40f0844 100644
--- a/internal/rpn/variables.go
+++ b/internal/rpn/variables.go
@@ -9,8 +9,8 @@ import (
// Error variables for external error checking.
var (
- ErrVariableNotFound = fmt.Errorf("variable not found")
- ErrInvalidVariableName = fmt.Errorf("invalid variable name")
+ ErrVariableNotFound = fmt.Errorf("variable not found")
+ ErrInvalidVariableName = fmt.Errorf("invalid variable name")
)
// Stack represents a simple float64 stack for RPN calculations.
@@ -71,7 +71,7 @@ func (s *Stack) Clear() {
// Variables stores variable name-value pairs for RPN calculations.
// It provides thread-safe access to variable storage.
type Variables struct {
- mu sync.RWMutex
+ mu sync.RWMutex
variables map[string]float64
}