github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/lang/test_structs.go (about) 1 package lang 2 3 /* 4 This test library relates to the testing framework within the murex 5 language itself rather than Go's test framework within the murex project. 6 7 The naming convention here is basically the inverse of Go's test naming 8 convention. ie Go source files will be named "test_unit.go" (because 9 calling it unit_test.go would mean it's a Go test rather than murex test) 10 and the code is named UnitTestPlans (etc) rather than TestUnitPlans (etc) 11 because the latter might suggest they would be used by `go test`. This 12 naming convention is a little counterintuitive but it at least avoids 13 naming conflicts with `go test`. 14 */ 15 16 import ( 17 "fmt" 18 "regexp" 19 "sync" 20 21 "github.com/lmorg/murex/lang/stdio" 22 "github.com/lmorg/murex/lang/types" 23 ) 24 25 // TestProperties are the values prescribed to an individual test case 26 type TestProperties struct { 27 Name string 28 out *TestChecks 29 err *TestChecks 30 exitNumPtr *int 31 exitNum int 32 HasRan bool 33 } 34 35 // TestChecks are the pipe streams and what test case to check against 36 type TestChecks struct { 37 stdio stdio.Io 38 Regexp *regexp.Regexp 39 Block []rune 40 RunBlock func(*Process, []rune, []byte) ([]byte, []byte, error) 41 } 42 43 // TestResult is a record for each test result 44 type TestResult struct { 45 Status TestStatus 46 TestName string 47 Message string 48 Exec string 49 Params []string 50 LineNumber int 51 ColNumber int 52 } 53 54 // TestResults is a class for the entire result set 55 type TestResults struct { 56 mutex sync.Mutex 57 results []*TestResult 58 } 59 60 // Add appends a result to TestResults 61 func (tr *TestResults) Add(result *TestResult) { 62 tr.mutex.Lock() 63 tr.results = append(tr.results, result) 64 tr.mutex.Unlock() 65 } 66 67 // Len returns the length of the results slice 68 func (tr *TestResults) Len() int { 69 tr.mutex.Lock() 70 i := len(tr.results) 71 tr.mutex.Unlock() 72 return i 73 } 74 75 // Dump returns the slice for runtime diagnositics 76 func (tr *TestResults) Dump() interface{} { 77 return tr.results 78 } 79 80 // TestStatus is a summarised stamp for a particular result 81 type TestStatus string 82 83 const ( 84 // TestPassed means the test has passed 85 TestPassed TestStatus = "PASSED" 86 87 // TestFailed means the test has failed 88 TestFailed TestStatus = "FAILED" 89 90 // TestError means there was an error running that test case 91 TestError TestStatus = "ERROR" 92 93 // TestState is reporting the output from test state blocks 94 TestState TestStatus = "STATE" 95 96 // TestInfo is for any additional information on a test that might help 97 // debug. This is only provided when `verbose` is enabled: `test verbose` 98 TestInfo TestStatus = "INFO" 99 100 // TestMissed means that test was not run (this is usually because 101 // it was inside a parent control block - eg if / switch / etc - 102 // which flowed down a different pathway. eg: 103 // 104 // if { true } else { out <test_example> "example" } 105 // 106 // `test_example` would not run because `if` would not run the 107 // `else` block. 108 TestMissed TestStatus = "MISSED" 109 ) 110 111 // Tests is a class of all the tests that needs to run inside a 112 // particular scope, plus all of it's results. 113 type Tests struct { 114 mutex sync.Mutex 115 test []*TestProperties 116 Results *TestResults 117 stateBlocks map[string][]rune 118 } 119 120 // NewTests creates a new testing scope for Murex's test suite.NewTests. 121 // Please note this should NOT be confused with Go tests (go test)! 122 func NewTests(p *Process) (tests *Tests) { 123 tests = new(Tests) 124 tests.stateBlocks = make(map[string][]rune) 125 126 if p.Id == ShellProcess.Id { 127 tests.Results = new(TestResults) 128 return 129 } 130 131 autoReport, err := p.Parent.Config.Get("test", "auto-report", types.Boolean) 132 if err != nil { 133 autoReport = true 134 } 135 136 if autoReport.(bool) { 137 tests.Results = new(TestResults) 138 } else { 139 tests.Results = ShellProcess.Tests.Results 140 } 141 142 return 143 } 144 145 // Define is the method used to define a new test case 146 func (tests *Tests) Define(name string, out *TestChecks, err *TestChecks, exitNum int) error { 147 tests.mutex.Lock() 148 149 var i int 150 for ; i < len(tests.test); i++ { 151 if tests.test[i].Name == name { 152 goto define 153 } 154 } 155 156 tests.test = append(tests.test, &TestProperties{ 157 Name: name, 158 out: out, 159 err: err, 160 exitNum: exitNum, 161 }) 162 163 tests.mutex.Unlock() 164 return nil 165 166 define: 167 tests.mutex.Unlock() 168 return fmt.Errorf("test already defined for '%s' in this scope", name) 169 } 170 171 // State creates a new test state 172 func (tests *Tests) State(name string, block []rune) error { 173 tests.mutex.Lock() 174 175 if len(tests.stateBlocks[name]) != 0 { 176 tests.mutex.Unlock() 177 return fmt.Errorf("test state already defined for '%s' in this scope", name) 178 } 179 180 if len(block) == 0 { 181 tests.mutex.Unlock() 182 return fmt.Errorf("test state for '%s' is an empty code block", name) 183 } 184 185 tests.stateBlocks[name] = block 186 tests.mutex.Unlock() 187 return nil 188 } 189 190 // Dump is used for `runtime --tests` 191 func (tests *Tests) Dump() interface{} { 192 tests.mutex.Lock() 193 194 names := make([]string, 0) 195 for _, ptr := range tests.test { 196 names = append(names, ptr.Name) 197 } 198 199 states := make(map[string]string) 200 for name, state := range tests.stateBlocks { 201 states[name] = string(state) 202 } 203 204 tests.mutex.Unlock() 205 206 return map[string]interface{}{ 207 "test": names, 208 "state": states, 209 "unit": GlobalUnitTests.Dump(), 210 } 211 }