Skip to content

Commit 97e4420

Browse files
authored
Merge pull request #3 from KyleKincer/transactions
WIP
2 parents 1df8927 + b32e8ba commit 97e4420

24 files changed

+586
-169
lines changed

CLAUDE.md

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ This project provides a complete testing framework for 4D applications featuring
1111
- **Flexible filtering** - Run specific test subsets by name, pattern, or tags
1212
- **Multiple output formats** - Human-readable and JSON output with terse/verbose modes
1313
- **CI/CD ready** - Structured JSON output for automated testing pipelines
14+
- **Automatic transaction management** - Test isolation with automatic rollback
15+
- **Manual transaction control** - Full transaction lifecycle management for advanced scenarios
1416

1517
## Running Tests
1618

@@ -92,4 +94,76 @@ testing/Project/Sources/Classes/
9294
└── [other test classes]
9395
```
9496

95-
The framework automatically discovers and runs any class ending with "Test" that contains methods starting with "test_".
97+
The framework automatically discovers and runs any class ending with "Test" that contains methods starting with "test_".
98+
99+
## Transaction Management
100+
101+
The framework provides automatic transaction management for test isolation and manual transaction control for advanced scenarios.
102+
103+
### Automatic Transaction Management
104+
105+
By default, each test runs in its own transaction that is automatically rolled back after completion, ensuring:
106+
- **Test Isolation**: Tests cannot interfere with each other's data
107+
- **Clean Environment**: Database state is restored after each test
108+
- **No Side Effects**: Failed tests don't leave partial data
109+
110+
### Controlling Transaction Behavior
111+
112+
Use comment-based annotations to control transaction behavior:
113+
114+
```4d
115+
Function test_withTransactions($t : cs:C1710.Testing)
116+
// Automatic transactions enabled (default)
117+
// Test data changes will be rolled back
118+
119+
Function test_withoutTransactions($t : cs:C1710.Testing)
120+
// #transaction: false
121+
// Disables automatic transaction management
122+
```
123+
124+
### Manual Transaction Control
125+
126+
The Testing context provides methods for manual transaction management:
127+
128+
```4d
129+
Function test_manualTransactions($t : cs:C1710.Testing)
130+
// #transaction: false
131+
132+
// Start transaction manually
133+
$t.startTransaction()
134+
135+
// Check transaction status
136+
If ($t.inTransaction())
137+
// Perform database operations
138+
End if
139+
140+
// Validate or cancel transaction
141+
If ($success)
142+
$t.validateTransaction()
143+
Else
144+
$t.cancelTransaction()
145+
End if
146+
147+
Function test_transactionWrapper($t : cs:C1710.Testing)
148+
// #transaction: false
149+
150+
// Execute operation within transaction (auto-rollback)
151+
var $success : Boolean
152+
$success:=$t.withTransaction(Formula(
153+
// Database operations here
154+
// Will be rolled back automatically
155+
))
156+
157+
// Execute operation with validation (persists data)
158+
$success:=$t.withTransactionValidate(Formula(
159+
// Database operations here
160+
// Will be validated if test succeeds
161+
))
162+
```
163+
164+
### Transaction Control Comments
165+
166+
| Comment | Effect |
167+
|---------|--------|
168+
| `// #transaction: false` | Disables automatic transactions |
169+
| No comment | Enables automatic transactions (default) |

testing/Project/Sources/Classes/TestRunner.4dm

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
property classStore : 4D:C1709.Object // Class store from the calling project
2-
property testSuites : Collection // Collection of cs.TestSuite
2+
property testSuites : Collection // Collection of cs._TestSuite
33
property results : Object // Test results summary
44
property outputFormat : Text // "human" or "json"
55
property verboseOutput : Boolean // Whether to include detailed information
@@ -32,7 +32,7 @@ Function run()
3232
This:C1470._logHeader()
3333
End if
3434

35-
var $testSuite : cs:C1710.TestSuite
35+
var $testSuite : cs:C1710._TestSuite
3636
For each ($testSuite; This:C1470.testSuites)
3737
$testSuite.run()
3838
This:C1470._collectSuiteResults($testSuite)
@@ -52,8 +52,8 @@ Function run()
5252
Function discoverTests()
5353
var $class : 4D:C1709.Class
5454
For each ($class; This:C1470._getTestClasses())
55-
var $testSuite : cs:C1710.TestSuite
56-
$testSuite:=cs:C1710.TestSuite.new($class; This:C1470.outputFormat; This:C1470.testPatterns; This:C1470)
55+
var $testSuite : cs:C1710._TestSuite
56+
$testSuite:=cs:C1710._TestSuite.new($class; This:C1470.outputFormat; This:C1470.testPatterns; This:C1470)
5757

5858
// Filter test suite based on patterns
5959
If (This:C1470._shouldIncludeTestSuite($testSuite))
@@ -114,7 +114,7 @@ Function _logHeader()
114114
LOG EVENT:C667(Into system standard outputs:K38:9; "Running tests...\r\n"; Information message:K38:1)
115115
LOG EVENT:C667(Into system standard outputs:K38:9; "\r\n"; Information message:K38:1)
116116

117-
Function _collectSuiteResults($testSuite : cs:C1710.TestSuite)
117+
Function _collectSuiteResults($testSuite : cs:C1710._TestSuite)
118118
var $suiteResult : Object
119119
$suiteResult:=New object:C1471(\
120120
"name"; $testSuite.class.name; \
@@ -123,7 +123,7 @@ Function _collectSuiteResults($testSuite : cs:C1710.TestSuite)
123123
"failed"; 0\
124124
)
125125

126-
var $testFunction : cs:C1710.TestFunction
126+
var $testFunction : cs:C1710._TestFunction
127127
For each ($testFunction; $testSuite.testFunctions)
128128
var $testResult : Object
129129
$testResult:=$testFunction.getResult()
@@ -319,7 +319,7 @@ Function _parseTestPatterns()
319319
End for each
320320
End if
321321

322-
Function _shouldIncludeTestSuite($testSuite : cs:C1710.TestSuite) : Boolean
322+
Function _shouldIncludeTestSuite($testSuite : cs:C1710._TestSuite) : Boolean
323323
// If no patterns specified, include all tests
324324
If (This:C1470.testPatterns.length=0)
325325
return True:C214
@@ -344,8 +344,8 @@ Function _shouldIncludeTestSuite($testSuite : cs:C1710.TestSuite) : Boolean
344344

345345
return False:C215
346346

347-
Function _patternMatchesAnyTestInSuite($testSuite : cs:C1710.TestSuite; $pattern : Text) : Boolean
348-
var $testFunction : cs:C1710.TestFunction
347+
Function _patternMatchesAnyTestInSuite($testSuite : cs:C1710._TestSuite; $pattern : Text) : Boolean
348+
var $testFunction : cs:C1710._TestFunction
349349
For each ($testFunction; $testSuite.testFunctions)
350350
var $fullTestName : Text
351351
$fullTestName:=$testSuite.class.name+"."+$testFunction.functionName
@@ -455,7 +455,7 @@ Function _parseTagList($tagString : Text) : Collection
455455

456456
return $tags
457457

458-
Function _shouldIncludeTestByTags($testFunction : cs:C1710.TestFunction) : Boolean
458+
Function _shouldIncludeTestByTags($testFunction : cs:C1710._TestFunction) : Boolean
459459
// Apply tag filtering logic to determine if test should be included
460460

461461
// If no tag filters specified, include all tests

testing/Project/Sources/Classes/Testing.4dm

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,90 @@ Function resetForNewTest()
3131

3232
Function run($name : Text; $subtest : 4D:C1709.Function)
3333
// This will be implemented later
34+
35+
// Transaction management methods for manual control
36+
37+
Function startTransaction() : Boolean
38+
// Start a transaction and return success status
39+
START TRANSACTION:C239
40+
return True
41+
42+
Function validateTransaction() : Boolean
43+
// Validate the current transaction
44+
VALIDATE TRANSACTION:C240
45+
return (OK=1)
46+
47+
Function cancelTransaction()
48+
// Cancel the current transaction
49+
CANCEL TRANSACTION:C241
50+
51+
Function inTransaction() : Boolean
52+
// Check if we're currently in a transaction
53+
return (In transaction:C397)
54+
55+
Function withTransaction($operation : 4D:C1709.Function) : Boolean
56+
// Execute an operation within a transaction
57+
// Returns true if operation succeeded and transaction was validated
58+
var $success : Boolean
59+
$success:=False
60+
61+
START TRANSACTION:C239
62+
63+
// Set up error handler to catch any errors during operation
64+
var $previousErrorHandler : Text
65+
$previousErrorHandler:=Method called on error:C704
66+
ON ERR CALL:C155("TestErrorHandler")
67+
68+
$operation.apply()
69+
70+
// Restore previous error handler
71+
If ($previousErrorHandler#"")
72+
ON ERR CALL:C155($previousErrorHandler)
73+
Else
74+
ON ERR CALL:C155("")
75+
End if
76+
77+
// Check if operation succeeded (no test failures)
78+
If (Not:C34(This:C1470.failed))
79+
CANCEL TRANSACTION:C241 // Always rollback for withTransaction
80+
$success:=True
81+
Else
82+
CANCEL TRANSACTION:C241
83+
$success:=False
84+
End if
85+
86+
return $success
87+
88+
Function withTransactionValidate($operation : 4D:C1709.Function) : Boolean
89+
// Execute an operation within a transaction and always validate on success
90+
// Useful for tests that need to persist data
91+
var $success : Boolean
92+
$success:=False
93+
94+
START TRANSACTION:C239
95+
96+
// Set up error handler to catch any errors during operation
97+
var $previousErrorHandler : Text
98+
$previousErrorHandler:=Method called on error:C704
99+
ON ERR CALL:C155("TestErrorHandler")
100+
101+
$operation.apply()
102+
103+
// Restore previous error handler
104+
If ($previousErrorHandler#"")
105+
ON ERR CALL:C155($previousErrorHandler)
106+
Else
107+
ON ERR CALL:C155("")
108+
End if
109+
110+
// Validate transaction if test succeeded
111+
If (Not:C34(This:C1470.failed))
112+
VALIDATE TRANSACTION:C240
113+
$success:=(OK=1)
114+
Else
115+
CANCEL TRANSACTION:C241
116+
$success:=False
117+
End if
118+
119+
return $success
34120

testing/Project/Sources/Classes/UnitStatsTracker.4dm

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Function resetStatistics()
1414
If (This:C1470._statsMap=Null:C1517)
1515
return
1616
End if
17-
var $statistic : cs:C1710.UnitStatsDetail
17+
var $statistic : cs:C1710._UnitStatsDetail
1818
For each ($statistic; OB Values:C1718(This:C1470._statsMap))
1919
If (OB Instance of:C1731($statistic.reset; 4D:C1709.Function))
2020
$statistic.reset()
@@ -26,14 +26,14 @@ Function createStatistic($name : Text) : cs:C1710.UnitStatsTracker
2626
$name - text - the name of the statistic to create
2727
returns - cs.UnitStatsTracker - This object to chain call
2828
*/
29-
This:C1470._statsMap[$name]:=cs:C1710.UnitStatsDetail.new()
29+
This:C1470._statsMap[$name]:=cs:C1710._UnitStatsDetail.new()
3030
return This:C1470
3131

32-
Function getStat($name : Text) : cs:C1710.UnitStatsDetail
32+
Function getStat($name : Text) : cs:C1710._UnitStatsDetail
3333
/* Gets a statistic. If one does not exist, a new statistic will
3434
be created an returned.
3535
$name - text - the name of the statistic to return
36-
returns - cs.UnitStatsDetail - a statistic
36+
returns - cs._UnitStatsDetail - a statistic
3737
*/
3838
If (Not:C34(This:C1470._doesStatisticExist($name)))
3939
This:C1470.createStatistic($name)
@@ -47,7 +47,7 @@ $functionName - text - the name of the statistic to update
4747
$parameters - collection - the collection of parameters to push onto the related statistic
4848
$returnValue - variant - value this method will return
4949
*/
50-
var $statistic : cs:C1710.UnitStatsDetail
50+
var $statistic : cs:C1710._UnitStatsDetail
5151
$statistic:=This:C1470.getStat($functionName)
5252
If ($statistic#Null:C1517)
5353
$statistic.appendCalledParameters((($parameters#Null:C1517) ? $parameters : []))
File renamed without changes.

testing/Project/Sources/Classes/ComprehensiveErrorTest.4dm renamed to testing/Project/Sources/Classes/_ComprehensiveErrorTest.4dm

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ Function test_null_class_handling($t : cs:C1710.Testing)
99
// We can't easily test with actual null class without causing errors,
1010
// but we can test with valid classes to ensure the structure works
1111
var $validClass : 4D:C1709.Class
12-
$validClass:=cs:C1710.ExampleTest
12+
$validClass:=cs:C1710._ExampleTest
1313

1414
$t.assert.isNotNull($t; $validClass; "Valid class should not be null")
1515

16-
var $suite : cs:C1710.TestSuite
17-
$suite:=cs:C1710.TestSuite.new($validClass; "human"; []; Null:C1517)
16+
var $suite : cs:C1710._TestSuite
17+
$suite:=cs:C1710._TestSuite.new($validClass; "human"; []; Null:C1517)
1818

1919
$t.assert.isNotNull($t; $suite; "TestSuite should handle valid class")
20-
$t.assert.areEqual($t; "ExampleTest"; $suite.class.name; "Should store class correctly")
20+
$t.assert.areEqual($t; "_ExampleTest"; $suite.class.name; "Should store class correctly")
2121

2222
Function test_empty_test_class($t : cs:C1710.Testing)
2323

@@ -29,19 +29,19 @@ Function test_empty_test_class($t : cs:C1710.Testing)
2929
$runner:=cs:C1710.TestRunner.new()
3030
$runner.testPatterns:=["nonexistent_pattern"]
3131

32-
var $suite : cs:C1710.TestSuite
33-
$suite:=cs:C1710.TestSuite.new(cs:C1710.ExampleTest; "human"; ["nonexistent_pattern"]; Null:C1517)
32+
var $suite : cs:C1710._TestSuite
33+
$suite:=cs:C1710._TestSuite.new(cs:C1710._ExampleTest; "human"; ["nonexistent_pattern"]; Null:C1517)
3434

3535
$t.assert.areEqual($t; 0; $suite.testFunctions.length; "Should have no test functions when pattern doesn't match")
3636

3737
Function test_malformed_test_methods($t : cs:C1710.Testing)
3838

3939
// Test that test discovery handles methods that don't follow the pattern
40-
var $suite : cs:C1710.TestSuite
41-
$suite:=cs:C1710.TestSuite.new(cs:C1710.ExampleTest; "human"; []; Null:C1517)
40+
var $suite : cs:C1710._TestSuite
41+
$suite:=cs:C1710._TestSuite.new(cs:C1710._ExampleTest; "human"; []; Null:C1517)
4242

4343
// All discovered methods should start with "test_"
44-
var $testFunction : cs:C1710.TestFunction
44+
var $testFunction : cs:C1710._TestFunction
4545
For each ($testFunction; $suite.testFunctions)
4646
$t.assert.isTrue($t; $testFunction.functionName="test_@"; "All discovered methods should start with test_, found: "+$testFunction.functionName)
4747
End for each
@@ -152,14 +152,14 @@ Function test_timing_precision($t : cs:C1710.Testing)
152152

153153
// Test that timing works correctly even for very fast tests
154154
var $exampleClass : 4D:C1709.Class
155-
$exampleClass:=cs:C1710.ExampleTest
156-
var $classInstance : cs:C1710.ExampleTest
157-
$classInstance:=cs:C1710.ExampleTest.new()
155+
$exampleClass:=cs:C1710._ExampleTest
156+
var $classInstance : cs:C1710._ExampleTest
157+
$classInstance:=cs:C1710._ExampleTest.new()
158158
var $testMethod : 4D:C1709.Function
159159
$testMethod:=$classInstance.test_areEqual_pass
160160

161-
var $testFunction : cs:C1710.TestFunction
162-
$testFunction:=cs:C1710.TestFunction.new($exampleClass; $classInstance; $testMethod; "test_areEqual_pass"; "")
161+
var $testFunction : cs:C1710._TestFunction
162+
$testFunction:=cs:C1710._TestFunction.new($exampleClass; $classInstance; $testMethod; "test_areEqual_pass"; "")
163163
$testFunction.run()
164164

165165
var $result : Object
@@ -202,7 +202,7 @@ Function test_memory_and_cleanup($t : cs:C1710.Testing)
202202
$t.assert.isTrue($t; $runner.testSuites.length>$initialSuites; "Should discover test suites")
203203

204204
// Verify each suite has proper structure
205-
var $suite : cs:C1710.TestSuite
205+
var $suite : cs:C1710._TestSuite
206206
For each ($suite; $runner.testSuites)
207207
$t.assert.isNotNull($t; $suite.class; "Suite should have class reference")
208208
$t.assert.isNotNull($t; $suite.classInstance; "Suite should have class instance")

testing/Project/Sources/Classes/ErrorHandlingTest.4dm renamed to testing/Project/Sources/Classes/_ErrorHandlingTest.4dm

File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)