mirror of https://github.com/01-edu/public.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
420 lines
11 KiB
420 lines
11 KiB
package lib |
|
|
|
import ( |
|
"bytes" |
|
"fmt" |
|
"io" |
|
"math/big" |
|
"math/rand" |
|
"os" |
|
"os/exec" |
|
"path" |
|
"reflect" |
|
"strconv" |
|
"strings" |
|
"time" |
|
"unicode" |
|
"unicode/utf8" |
|
) |
|
|
|
const ( |
|
MinInt = ^MaxInt |
|
MaxInt = 1<<63 - 1 |
|
|
|
StrLen = 13 // Default length of random strings |
|
SliceLen = 8 // Default length of slices |
|
) |
|
|
|
var ( |
|
nsSince1970 = time.Now().UnixNano() |
|
bigRand = rand.New(rand.NewSource(nsSince1970)) |
|
|
|
// charsets |
|
Digit = RuneRange('0', '9') // Decimal digit characters |
|
Lower = RuneRange('a', 'z') // Lowercase latin alphabet characters |
|
Upper = RuneRange('A', 'Z') // Uppercase latin alphabet characters |
|
ASCII = RuneRange(' ', '~') // ASCII printable characters |
|
Space = strings.Repeat(" ", StrLen) // Spaces characters |
|
Basic = Lower + Upper // Lower and Upper characters |
|
Alnum = Basic + Digit // Basic and Digit characters |
|
Words = Alnum + Space // Alnum and Space characters |
|
) |
|
|
|
func init() { |
|
rand.Seed(nsSince1970) |
|
} |
|
|
|
// RuneRange returns a string containing all the valid runes from a to b. |
|
func RuneRange(a, b rune) string { |
|
var s []rune |
|
for { |
|
if utf8.ValidRune(a) { |
|
s = append(s, a) |
|
} |
|
if a == b { |
|
return string(s) |
|
} |
|
if a < b { |
|
a++ |
|
} else { |
|
a-- |
|
} |
|
} |
|
} |
|
|
|
// IntRange returns a slice containing all the int from a to b. |
|
func IntRange(a, b int) (s []int) { |
|
for { |
|
s = append(s, a) |
|
if a == b { |
|
return |
|
} |
|
if a < b { |
|
a++ |
|
} else { |
|
a-- |
|
} |
|
} |
|
} |
|
|
|
// RandIntBetween returns a random int between a and b included. |
|
func RandIntBetween(a, b int) int { |
|
if a > 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) |
|
} |
|
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 "<nil>" |
|
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() |
|
panicIfNotNil(err) |
|
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 panicIfNotNil(err error) { |
|
if err != nil { |
|
panic(err) |
|
} |
|
} |
|
|
|
func ChallengeMainStdin(exercise, input string, args ...string) { |
|
run := func(pkg string) (string, int) { |
|
binaryPath := path.Join(os.TempDir(), "binaries", path.Base(pkg)) |
|
panicIfNotNil(exec.Command("go", "build", "-o", binaryPath, pkg).Run()) |
|
cmd := exec.Command(binaryPath, args...) |
|
if input != "" { |
|
cmd.Stdin = bytes.NewBufferString(input) |
|
} |
|
b, err := cmd.CombinedOutput() |
|
if err != nil { |
|
if ee, ok := err.(*exec.ExitError); ok { |
|
return string(b), ee.ExitCode() |
|
} |
|
panic(err) |
|
} |
|
return string(b), 0 |
|
} |
|
console := func(out string) string { |
|
var quotedArgs []string |
|
for _, arg := range args { |
|
quotedArgs = append(quotedArgs, strconv.Quote(arg)) |
|
} |
|
s := "\n$ " |
|
if input != "" { |
|
s += "echo -ne " + strconv.Quote(input) + " | " |
|
} |
|
return fmt.Sprintf(s+"./%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(path.Join("student", exercise)) |
|
solution, solutionCode := run(path.Join("github.com/01-edu/public/test-go/solutions", exercise+"_prog")) |
|
if solutionCode == 0 { |
|
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)) |
|
} |
|
} else { |
|
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)) |
|
} |
|
} |
|
if student != solution { |
|
Fatalln("Your program output is not correct :\n" + |
|
console(student) + "\n\n" + |
|
"Expected :\n" + |
|
console(solution)) |
|
} |
|
} |
|
|
|
// GCD returns greatest common divisor of a and b. |
|
func GCD(a, b int) int { |
|
for a != b { |
|
if a > b { |
|
a -= b |
|
} else { |
|
b -= a |
|
} |
|
} |
|
return a |
|
} |
|
|
|
func ChallengeMain(exercise string, args ...string) { |
|
ChallengeMainStdin(exercise, "", args...) |
|
} |
|
|
|
// TODO: check unhandled errors on all solutions (it should contains "ERROR" on the first line to prove we correctly handle the error) |
|
// TODO: remove the number of rand functions, refactor test cases (aka "table")
|
|
|