From 37eaf23e3f3a3747b176cc2f52adc5027b999366 Mon Sep 17 00:00:00 2001 From: Xavier Petit <32063953+xpetit@users.noreply.github.com> Date: Mon, 27 Apr 2020 00:38:26 +0200 Subject: [PATCH] Add lib (was z01 before) with utilities to help testing --- tests/go/lib/lib.go | 408 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 408 insertions(+) create mode 100644 tests/go/lib/lib.go diff --git a/tests/go/lib/lib.go b/tests/go/lib/lib.go new file mode 100644 index 00000000..461bbdeb --- /dev/null +++ b/tests/go/lib/lib.go @@ -0,0 +1,408 @@ +package lib + +import ( + "fmt" + "io" + "math/big" + "math/bits" + "math/rand" + "os" + "os/exec" + "reflect" + "strconv" + "strings" + "time" + "unicode" + "unicode/utf8" +) + +const ( + IntSize = bits.UintSize - 1 // 31 or 63 + + // 32 bits 64 bits + MinInt = -1 << IntSize // -2147483648 -9223372036854775808 + MaxInt = 1< b { + a, b = b, a + } + n := big.NewInt(int64(b)) // b + n.Sub(n, big.NewInt(int64(a))) // b-a + n.Add(n, big.NewInt(1)) // b-a+1 + n.Rand(bigRand, n) // 0 <= n <= b-a + n.Add(n, big.NewInt(int64(a))) // a <= n <= b + return int(n.Int64()) +} + +// RandPosZ returns a random int between 0 and MaxInt included. +func RandPosZ() int { return RandIntBetween(0, MaxInt) } + +// RandPos returns a random int between 1 and MaxInt included. +func RandPos() int { return RandIntBetween(1, MaxInt) } + +// RandInt returns a random int between MinInt and MaxInt included. +func RandInt() int { return RandIntBetween(MinInt, MaxInt) } + +// RandNeg returns a random int between MinInt and 1 included. +func RandNeg() int { return RandIntBetween(MinInt, 1) } + +// RandNegZ returns a random int between MinInt and 0 included. +func RandNegZ() int { return RandIntBetween(MinInt, 0) } + +// MakeIntFunc returns a slice of ints created by f. +func MakeIntFunc(f func() int) (s []int) { + i := 0 + for i < SliceLen { + s = append(s, f()) + i++ + } + return +} + +// MultRandPosZ returns a slice of random ints between 0 and MaxInt included. +func MultRandPosZ() []int { return MakeIntFunc(RandPosZ) } + +// MultRandPos returns a slice of random ints between 1 and MaxInt included. +func MultRandPos() []int { return MakeIntFunc(RandPos) } + +// MultRandInt returns a slice of random ints between MinInt and MaxInt included. +func MultRandInt() []int { return MakeIntFunc(RandInt) } + +// MultRandNeg returns a slice of random ints between MinInt and 1 included. +func MultRandNeg() []int { return MakeIntFunc(RandNeg) } + +// MultRandNegZ returns a slice of random ints between MinInt and 0 included. +func MultRandNegZ() []int { return MakeIntFunc(RandNegZ) } + +// MultRandIntBetween returns a slice of random ints between a and b included. +func MultRandIntBetween(a, b int) []int { + return MakeIntFunc(func() int { return RandIntBetween(a, b) }) +} + +// RandRune returns a random printable rune +// (although you may not have the corresponding glyph). +// One-in-ten chance to get a rune higher than 0x10000 (1<<16). +func RandRune() rune { + ranges := unicode.PrintRanges + table := ranges[rand.Intn(len(ranges))] + if rand.Intn(10) == 0 { + r := table.R32[rand.Intn(len(table.R32))] + n := uint32(rand.Intn(int((r.Hi-r.Lo)/r.Stride) + 1)) + return rune(r.Lo + n*r.Stride) + } else { + r := table.R16[rand.Intn(len(table.R16))] + n := uint16(rand.Intn(int((r.Hi-r.Lo)/r.Stride) + 1)) + return rune(r.Lo + n*r.Stride) + } +} + +// RandStr returns a string with l random characters taken from chars. +// If chars is empty, the characters are random printable runes. +func RandStr(l int, chars string) string { + if l <= 0 { + return "" + } + dst := make([]rune, l) + if chars == "" { + for i := range dst { + dst[i] = RandRune() + } + } else { + src := []rune(chars) + for i := range dst { + r := rand.Intn(len(src)) + dst[i] = src[r] + } + } + return string(dst) +} + +// RandDigit returns a string containing random decimal digit characters. +func RandDigit() string { return RandStr(StrLen, Digit) } + +// RandLower returns a string containing random lowercase latin alphabet characters. +func RandLower() string { return RandStr(StrLen, Lower) } + +// RandUpper returns a string containing random uppercase latin alphabet characters. +func RandUpper() string { return RandStr(StrLen, Upper) } + +// RandASCII returns a string containing random ASCII printable characters. +func RandASCII() string { return RandStr(StrLen, ASCII) } + +// RandSpace returns a string containing random spaces characters. +func RandSpace() string { return RandStr(StrLen, Space) } + +// RandBasic returns a string containing random lower and upper characters. +func RandBasic() string { return RandStr(StrLen, Basic) } + +// RandAlnum returns a string containing random basic and digit characters. +func RandAlnum() string { return RandStr(StrLen, Alnum) } + +// RandWords returns a string containing random alphanumeric and space characters. +func RandWords() string { return RandStr(StrLen, Words) } + +// MakeStrFunc returns a slice of strings created by f. +func MakeStrFunc(f func() string) (s []string) { + i := 0 + for i < StrLen { + s = append(s, f()) + i++ + } + return +} + +// MultRandDigit returns a slice of strings containing random Decimal digit characters. +func MultRandDigit() []string { return MakeStrFunc(RandDigit) } + +// MultRandLower returns a slice of strings containing random Lowercase latin alphabet. +func MultRandLower() []string { return MakeStrFunc(RandLower) } + +// MultRandUpper returns a slice of strings containing random Uppercase latin alphabet. +func MultRandUpper() []string { return MakeStrFunc(RandUpper) } + +// MultRandASCII returns a slice of strings containing random ASCII printable characters. +func MultRandASCII() []string { return MakeStrFunc(RandASCII) } + +// MultRandSpace returns a slice of strings containing random Spaces characters. +func MultRandSpace() []string { return MakeStrFunc(RandSpace) } + +// MultRandBasic returns a slice of strings containing random Lower and Upper characters. +func MultRandBasic() []string { return MakeStrFunc(RandBasic) } + +// MultRandAlnum returns a slice of strings containing random Basic and Digit characters. +func MultRandAlnum() []string { return MakeStrFunc(RandAlnum) } + +// MultRandWords returns a slice of strings containing random Alnum and Space characters. +func MultRandWords() []string { return MakeStrFunc(RandWords) } + +func Format(a ...interface{}) string { + ss := make([]string, len(a)) + for i, v := range a { + switch v.(type) { + case nil: + ss[i] = "nil" // instead of "" + case + string, + byte, // uint8 + rune: // int32 + + // string : a double-quoted string safely escaped with Go syntax + // byte, rune : a single-quoted character literal safely escaped with Go syntax + ss[i] = fmt.Sprintf("%q", v) + default: + // a Go-syntax representation of the value + ss[i] = fmt.Sprintf("%#v", v) + } + } + return strings.Join(ss, ", ") +} + +var valueOf = reflect.ValueOf + +func Call(fn interface{}, args []interface{}) []interface{} { + // Convert args from []interface{} to []reflect.Value + vals := make([]reflect.Value, len(args)) + for i, v := range args { + if v != nil { + vals[i] = valueOf(v) + } else { + vals[i] = reflect.Zero(reflect.TypeOf((*interface{})(nil)).Elem()) + } + } + + vals = valueOf(fn).Call(vals) + + // Convert the return values from []reflect.Value to []interface{} + result := make([]interface{}, len(vals)) + for i, v := range vals { + result[i] = v.Interface() + } + return result +} + +type Output struct { + Results []interface{} + Stdout string +} + +func Monitor(fn interface{}, args []interface{}) (out Output) { + old := os.Stdout + r, w, err := os.Pipe() + if err != nil { + Fatalln("Cannot create pipe.") + } + os.Stdout = w + out.Results = Call(fn, args) + outC := make(chan string) + var buf strings.Builder + go func() { + io.Copy(&buf, r) + outC <- buf.String() + }() + os.Stdout = old + w.Close() + out.Stdout = <-outC + return out +} + +func Challenge(name string, fn1, fn2 interface{}, args ...interface{}) { + st1 := Monitor(fn1, args) + st2 := Monitor(fn2, args) + if !reflect.DeepEqual(st1.Results, st2.Results) { + Fatalf("%s(%s) == %s instead of %s\n", + name, + Format(args...), + Format(st1.Results...), + Format(st2.Results...), + ) + } + if !reflect.DeepEqual(st1.Stdout, st2.Stdout) { + Fatalf("%s(%s) prints:\n%s\ninstead of:\n%s\n", + name, + Format(args...), + Format(st1.Stdout), + Format(st2.Stdout), + ) + } +} + +func Fatal(a ...interface{}) { + fmt.Fprint(os.Stderr, a...) + os.Exit(1) +} + +func Fatalln(a ...interface{}) { + fmt.Fprintln(os.Stderr, a...) + os.Exit(1) +} + +func Fatalf(format string, a ...interface{}) { + fmt.Fprintf(os.Stderr, format, a...) + os.Exit(1) +} + +func ChallengeMain(exercise string, args ...string) { + run := func(name string) (string, int) { + b, err := exec.Command(name, args...).CombinedOutput() + if err != nil { + if ee, ok := err.(*exec.ExitError); ok { + return string(b), ee.ExitCode() + } + Fatalln(err) + } + return string(b), 0 + } + console := func(out string) string { + var quotedArgs []string + for _, arg := range args { + quotedArgs = append(quotedArgs, strconv.Quote(arg)) + } + return fmt.Sprintf("\n$ ./%s %s\n%s$ ", exercise, strings.Join(quotedArgs, " "), out) + } + code := func(code int) string { + return fmt.Sprintf("echo $?\n%d\n$", code) + } + student, studentCode := run("./student") + solution, solutionCode := run("./solution") + if solutionCode != 0 { + if studentCode == 0 { + Fatalln("Your program does not fail when it should (with a non-zero exit status) :" + "\n" + + console(student) + + code(studentCode) + "\n\n" + + "Expected :\n" + + console(solution) + + code(solutionCode) + "\n\n" + + "Another example :\n" + + console("ERROR\n") + + code(1)) + } + firstLine := strings.SplitN(student, "\n", 1)[0] + handledError := strings.Contains(firstLine, "ERROR") + if !handledError { + Fatalln(`Your program does not handle the error, the first line of the output must contain "ERROR" :` + "\n" + + console(student) + + code(studentCode) + "\n\n" + + "Expected :\n" + + console(solution) + + code(solutionCode) + "\n\n" + + "Another example :\n" + + console("ERROR\n") + + code(1)) + } + } + if studentCode != 0 { + Fatalln("Your program fails (non-zero exit status) when it should not :\n" + + console(student) + + code(studentCode) + "\n\n" + + "Expected :\n" + + console(solution) + + code(solutionCode)) + } + if student != solution { + Fatalln("Your program output is not correct :\n" + + console(student) + "\n\n" + + "Expected :\n" + + console(solution)) + } +} + +// TODO: check unhandled errors on all solutions (it should contains "ERROR" on the first line to prove we correctly handle the error)