github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/lang/test_results.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  	"errors"
    18  	"fmt"
    19  	"strings"
    20  
    21  	"github.com/lmorg/murex/config"
    22  	"github.com/lmorg/murex/lang/ref"
    23  	"github.com/lmorg/murex/lang/stdio"
    24  	"github.com/lmorg/murex/lang/types"
    25  	"github.com/lmorg/murex/utils/ansi/codes"
    26  	"github.com/lmorg/murex/utils/json"
    27  )
    28  
    29  // SetStreams is called when a particular test case is run. eg
    30  //
    31  //	out <test_example> "Run this test"
    32  func (tests *Tests) SetStreams(name string, stdout, stderr stdio.Io, exitNumPtr *int) error {
    33  	tests.mutex.Lock()
    34  
    35  	var i int
    36  	for ; i < len(tests.test); i++ {
    37  		if tests.test[i].Name == name {
    38  			goto set
    39  		}
    40  	}
    41  
    42  	tests.mutex.Unlock()
    43  	return errors.New("Test named but there is no test defined for '" + name + "'.")
    44  
    45  set:
    46  	tests.test[i].out.stdio = stdout
    47  	tests.test[i].err.stdio = stderr
    48  	tests.test[i].exitNumPtr = exitNumPtr
    49  	tests.mutex.Unlock()
    50  	return nil
    51  }
    52  
    53  // AddResult is called after the test has run so the result can be recorded
    54  func (tests *Tests) AddResult(test *TestProperties, p *Process, status TestStatus, message string) {
    55  	// p.FileRef might be nil for missed tests
    56  	var fileRef ref.File
    57  	if p.FileRef != nil {
    58  		fileRef = *p.FileRef
    59  	}
    60  
    61  	tests.Results.Add(&TestResult{
    62  		TestName:   test.Name,
    63  		Exec:       p.Name.String(),
    64  		Params:     p.Parameters.StringArray(),
    65  		LineNumber: fileRef.Line,
    66  		ColNumber:  fileRef.Column,
    67  		Status:     status,
    68  		Message:    message,
    69  	})
    70  }
    71  
    72  // WriteResults is the reporting tool
    73  func (tests *Tests) WriteResults(config *config.Config, pipe stdio.Io) error {
    74  	params := func(exec string, params []string) (s string) {
    75  		if len(params) > 1 {
    76  			//s = exec + " '" + strings.Join(params, "' '") + "'"
    77  			s = exec + " " + strings.Join(params, " ")
    78  		} else {
    79  			s = exec
    80  		}
    81  		if len(s) > 50 {
    82  			s = s[:49] + "…"
    83  		}
    84  		return
    85  	}
    86  
    87  	escape := func(s string) string {
    88  		s = strings.Replace(s, "\n", `\n`, -1)
    89  		s = strings.Replace(s, "\r", `\r`, -1)
    90  		s = strings.Replace(s, "\t", `\t`, -1)
    91  		return s
    92  	}
    93  
    94  	left := func(s string) string {
    95  		crop, err := config.Get("test", "crop-message", types.Integer)
    96  		if err != nil || crop.(int) == 0 {
    97  			return s
    98  		}
    99  
   100  		if len(s) < crop.(int) {
   101  			return s
   102  		}
   103  
   104  		return s[:crop.(int)-1] + "…"
   105  	}
   106  
   107  	tests.mutex.Lock()
   108  	defer tests.mutex.Unlock()
   109  
   110  	if tests.Results.Len() == 0 {
   111  		return nil
   112  	}
   113  
   114  	reportType, err := config.Get("test", "report-format", types.String)
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	reportPipe, err := config.Get("test", "report-pipe", types.String)
   120  	if err != nil {
   121  		reportPipe = ""
   122  	}
   123  
   124  	verbose, err := config.Get("test", "verbose", types.Boolean)
   125  	if err != nil {
   126  		verbose = false
   127  	}
   128  
   129  	ansiColour, err := config.Get("shell", "color", types.Boolean)
   130  	if err != nil {
   131  		ansiColour = false
   132  	}
   133  
   134  	if reportPipe.(string) != "" {
   135  		pipe, err = GlobalPipes.Get(reportPipe.(string))
   136  		if err != nil {
   137  			return err
   138  		}
   139  	}
   140  
   141  	defer func() {
   142  		tests.Results.results = make([]*TestResult, 0)
   143  	}()
   144  
   145  	switch reportType.(string) {
   146  	case "json":
   147  		pipe.SetDataType(types.Json)
   148  
   149  		b, err := json.Marshal(tests.Results.results, pipe.IsTTY())
   150  		if err != nil {
   151  			return err
   152  		}
   153  
   154  		_, err = pipe.Writeln(b)
   155  		return err
   156  
   157  	case "table":
   158  		pipe.SetDataType(types.Generic)
   159  
   160  		//if reportPipe.(string) == "" {
   161  		//	pipe.Writeln([]byte(consts.TestTableHeadings))
   162  		//}
   163  
   164  		var colour, reset string
   165  		if ansiColour.(bool) {
   166  			reset = codes.Reset
   167  		}
   168  		for _, r := range tests.Results.results {
   169  			if !verbose.(bool) && (r.Status == TestMissed || r.Status == TestInfo) {
   170  				continue
   171  			}
   172  
   173  			if ansiColour.(bool) {
   174  				switch r.Status {
   175  				case TestPassed:
   176  					colour = codes.FgGreen
   177  				case TestFailed, TestError:
   178  					colour = codes.FgRed
   179  				case TestMissed, TestInfo:
   180  					colour = codes.FgBlue
   181  				case TestState:
   182  					colour = codes.FgYellow
   183  				}
   184  			}
   185  
   186  			s := fmt.Sprintf("%s[%s%-6s%s] %-10s %4d:%-5d %-50s %s",
   187  				reset, colour, r.Status, reset,
   188  				r.TestName,
   189  				r.LineNumber, r.ColNumber,
   190  				params(r.Exec, r.Params),
   191  				left(escape(r.Message)),
   192  			)
   193  
   194  			pipe.Writeln([]byte(s))
   195  
   196  		}
   197  		return nil
   198  
   199  	case "csv":
   200  		pipe.SetDataType("csv")
   201  		s := fmt.Sprintf(`%s %-13s %-53s %-7s %-7s %s`,
   202  			`"Status",`,
   203  			`"Test Name",`,
   204  			`"Process",`,
   205  			`"Line",`,
   206  			`"Col.",`,
   207  			`"Message"`,
   208  		)
   209  		pipe.Writeln([]byte(s))
   210  
   211  		for _, r := range tests.Results.results {
   212  			if !verbose.(bool) && (r.Status == TestMissed || r.Status == TestInfo) {
   213  				continue
   214  			}
   215  
   216  			s = fmt.Sprintf(`%-9s %-13s %-53s %6d, %6d, %s`,
   217  				`"`+r.Status+`",`,
   218  				`"`+r.TestName+`",`,
   219  				`"`+params(r.Exec, r.Params)+`",`,
   220  				r.LineNumber,
   221  				r.ColNumber,
   222  				`"`+strings.Replace(escape(r.Message), `"`, `""`, -1)+`"`,
   223  			)
   224  
   225  			pipe.Writeln([]byte(s))
   226  
   227  		}
   228  		return nil
   229  
   230  	default:
   231  		return errors.New("invalid report type requested via `config set test report-format`")
   232  	}
   233  }