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  }