Gopher2600/regression/regression.go
steve 17b5c599f2 o paths
- implemented paths package
    - handles building of paths using the most appropriate config path -
	the local directory or the user's home directory
2020-01-05 18:58:39 +00:00

232 lines
6.3 KiB
Go

package regression
import (
"fmt"
"gopher2600/database"
"gopher2600/debugger/colorterm/ansi"
"gopher2600/errors"
"gopher2600/paths"
"io"
"os"
"sort"
"strconv"
)
// the location of the regressionDB file and the location of any regression
// scripts. these should be wrapped by paths.ResourcePath()
const regressionDBFile = "regressionDB"
const regressionScripts = "regressionScripts"
// Regressor is the generic entry type in the regressionDB
//
// * exported because we use it directly from the main package
type Regressor interface {
database.Entry
// perform the regression test for the regression type. the newRegression
// flag is for convenience really (or "logical binding", as the structured
// programmers would have it)
//
// message is the string that is to be printed during the regression
//
// returns: success boolean; any failure message (not always appropriate;
// and error state
regress(newRegression bool, output io.Writer, message string) (bool, string, error)
}
// when starting a database session we need to register what entries we will
// find in the database
func initDBSession(db *database.Session) error {
if err := db.RegisterEntryType(frameEntryID, deserialiseFrameEntry); err != nil {
return err
}
if err := db.RegisterEntryType(playbackEntryID, deserialisePlaybackEntry); err != nil {
return err
}
// make sure regression script directory exists
if err := os.MkdirAll(paths.ResourcePath(regressionScripts), 0755); err != nil {
msg := fmt.Sprintf("regression script directory: %s", err)
return errors.New(errors.RegressionError, msg)
}
return nil
}
// RegressList displays all entries in the database
func RegressList(output io.Writer) error {
if output == nil {
return errors.New(errors.PanicError, "RegressList()", "io.Writer should not be nil (use a nopWriter)")
}
db, err := database.StartSession(paths.ResourcePath(regressionDBFile), database.ActivityReading, initDBSession)
if err != nil {
return err
}
defer db.EndSession(false)
return db.List(output)
}
// RegressAdd adds a new regression handler to the database
func RegressAdd(output io.Writer, reg Regressor) error {
if output == nil {
return errors.New(errors.PanicError, "RegressAdd()", "io.Writer should not be nil (use nopWriter)")
}
db, err := database.StartSession(paths.ResourcePath(regressionDBFile), database.ActivityCreating, initDBSession)
if err != nil {
return err
}
defer db.EndSession(true)
msg := fmt.Sprintf("adding: %s", reg)
_, _, err = reg.regress(true, output, msg)
if err != nil {
return err
}
output.Write([]byte(ansi.ClearLine))
output.Write([]byte(fmt.Sprintf("\radded: %s\n", reg)))
return db.Add(reg)
}
// RegressDelete removes a cartridge from the regression db
func RegressDelete(output io.Writer, confirmation io.Reader, key string) error {
if output == nil {
return errors.New(errors.PanicError, "RegressDelete()", "io.Writer should not be nil (use nopWriter)")
}
v, err := strconv.Atoi(key)
if err != nil {
msg := fmt.Sprintf("invalid key [%s]", key)
return errors.New(errors.RegressionError, msg)
}
db, err := database.StartSession(paths.ResourcePath(regressionDBFile), database.ActivityModifying, initDBSession)
if err != nil {
return err
}
defer db.EndSession(true)
ent, err := db.SelectKeys(nil, v)
if err != nil {
if !errors.Is(err, errors.DatabaseSelectEmpty) {
return err
}
// select returned no entries; create DatabaseKeyError and wrap it in a
// RegressionError
return errors.New(errors.RegressionError, errors.New(errors.DatabaseKeyError, v))
}
output.Write([]byte(fmt.Sprintf("%s\ndelete? (y/n): ", ent)))
confirm := make([]byte, 32)
_, err = confirmation.Read(confirm)
if err != nil {
return err
}
if confirm[0] == 'y' || confirm[0] == 'Y' {
err = db.Delete(v)
if err != nil {
fmt.Println(1)
return err
}
output.Write([]byte(fmt.Sprintf("deleted test #%s from regression database\n", key)))
}
return nil
}
// RegressRunTests runs all the tests in the regression database
// o filterKeys list specified which entries to test. an empty keys list means that
// every entry should be tested
func RegressRunTests(output io.Writer, verbose bool, failOnError bool, filterKeys []string) error {
if output == nil {
return errors.New(errors.PanicError, "RegressRunEntries()", "io.Writer should not be nil (use nopWriter)")
}
db, err := database.StartSession(paths.ResourcePath(regressionDBFile), database.ActivityReading, initDBSession)
if err != nil {
return err
}
defer db.EndSession(false)
// make sure any supplied keys list is in order
keysV := make([]int, 0, len(filterKeys))
for k := range filterKeys {
v, err := strconv.Atoi(filterKeys[k])
if err != nil {
msg := fmt.Sprintf("invalid key [%s]", filterKeys[k])
return errors.New(errors.RegressionError, msg)
}
keysV = append(keysV, v)
}
sort.Ints(keysV)
numSucceed := 0
numFail := 0
numError := 0
numSkipped := 0
defer func() {
output.Write([]byte(fmt.Sprintf("regression tests: %d succeed, %d fail, %d skipped", numSucceed, numFail, numSkipped)))
if numError > 0 {
output.Write([]byte(" [with errors]"))
}
output.Write([]byte("\n"))
}()
onSelect := func(ent database.Entry) (bool, error) {
// datbase entry should also satisfy Regressor interface
reg, ok := ent.(Regressor)
if !ok {
return false, errors.New(errors.PanicError, "RegressRunTests()", "database entry does not satisfy Regressor interface")
}
// run regress() function with message. message does not have a
// trailing newline
msg := fmt.Sprintf("running: %s", reg)
ok, failm, err := reg.regress(false, output, msg)
// once regress() has completed we clear the line ready for the
// completion message
output.Write([]byte(ansi.ClearLine))
// print completion message depending on result of regress()
if err != nil {
numError++
output.Write([]byte(fmt.Sprintf("\r ERROR: %s\n", reg)))
// output any error message on following line
if verbose {
output.Write([]byte(fmt.Sprintf("%s\n", err)))
}
if failOnError {
return false, nil
}
} else if !ok {
numFail++
output.Write([]byte(fmt.Sprintf("\rfailure: %s\n", reg)))
if verbose && failm != "" {
output.Write([]byte(fmt.Sprintf(" ^^ %s\n", failm)))
}
} else {
numSucceed++
output.Write([]byte(fmt.Sprintf("\rsucceed: %s\n", reg)))
}
return true, nil
}
db.SelectKeys(onSelect, keysV...)
return nil
}