diff --git a/acas b/acas new file mode 100644 index 00000000000..7e5ed6f1a14 --- /dev/null +++ b/acas @@ -0,0 +1,122 @@ +1d926698 (HEAD -> master, origin/master, origin/HEAD) ㅠㅠㅠㅠㅠㅠ +4bd26ea5 misc genstd mapping.go 수정 +d62b5ec6 오류 없음. 현재 포멧팅 중 +eff34600 good +797f24ee sync +192f49aa Delete gnovm/stdlibs/testing/string_simulator directory +2a13333e Delete gnovm/stdlibs/testing/runner directory +250f19ca Delete gnovm/stdlibs/testing/queue directory +5c6678f3 Delete gnovm/stdlibs/testing/pseudo_coverage directory +c33face8 Delete gnovm/stdlibs/testing/generate directory +c14c5675 Delete gnovm/stdlibs/testing/crashing directory +8343a89e Delete gnovm/stdlibs/testing/craash_catch_test directory +26b2b1db fixed genom_alg +e177caeb . +15ff5c4e statemachine의 자료구조 설정 완료 +65a213e1 genom alg +0fe973ff 유전 알고리즘 정상화 +0635997e good +c6bd5009 Merge pull request #2 from pluto1011/master +a4412dc5 99%test +e6ccbea1 data structrue complete +b9fb97be Merge pull request #1 from pluto1011/master +9c5e3f68 protoType +9313db14 pushed +c17e9caf counting sort +f159794d simulate coverage +abb931a6 reflect Tf +0f74272d crash-check +f27b1827 fix: bit shifting const expr overflow (#3192) +30aac2a8 feat(gnovm): returns error like in Golang when assignment with a function which does not return any value (#3049) +a723673b chore: revert "fix(portal-loop): hotfix revert "chore: rename r/manfred -> r/moul (#2820)" (#2865)" (#3024) +2f162b44 chore: remove ancient docker integration in `misc` (#3172) +c6f8dd4e fix: const conversions for 32 bits (#3186) +d0493dff fix: typed const conversion validation (#3117) +77e66061 fix: invoke user recover with implicit panics (#3067) +b3e4aed7 chore: (portal loop): Fixing Portal loop prod config (#3181) +139ba068 chore: sync portal loop machine and `gnolang/gno` repo (#3173) +db1d6990 feat: r/docs/home -> r/docs (#3175) +4d803786 docs(std/testing): fix NewUserRealm reference usage (#3178) +549da06d chore(otel): Open Telemetry metrics fixed and provided with demo example (#3038) +dc65f912 feat(gnovm): sync code AssignStmt - ValueDecl (#3017) +7718bc37 feat(p/int256): Optimize `int256` with two's complement implementation (#2846) +889082fa feat(examples): add source code view doc, add `r/` README (#3163) +7e5de12c chore: move `hof` under `r/leon` (#3167) +2c323f48 ci: only run fossa action on workflow_dispatch (#3125) +4646ae67 feat: add r/docs/home (#3160) +732bb0bc refactor: remove useless code and fix a test (#3159) +7188b1cd feat: add gnohealth cli tool (#3158) +b3800b7d feat(examples): mirror realm (#3156) +1e2929bb feat: bump codecov to v5 (#3152) +02467612 fix: branch stmts (#3043) +6a13619d feat(examples): add hello_world, update `r/demo/event` (#3130) +a1a7cb3a feat: add bug label automatically to the bug report template (#3132) +a1812af6 feat: add p/moul/mdtable (#3100) +6c5329d7 docs(gno-js): Add provider instantiation docs (#2427) +38736e75 chore(deps): bump the actions group across 1 directory with 2 updates (#3114) +1993c69c feat(examples): hall of fame (#2842) +6c3cc029 chore(examples): update README (#3116) +bd1d76e0 feat(examples): add haystack package and realm (#3082) +3bb666c0 feat(examples): grc20 refactor (#2983) +36cdadb8 feat: add r/sys/params + params genesis support (#3003) +60304df0 chore(p/grc721): Distinct Event Types for GRC721 Functions (#3102) +5b64aa9a feat: add initial `test5.gno.land` deployment (#3092) +5f85d50e feat: add p/moul/realmpath (#3094) +4f27a572 fix(cmd/gno/clean): allow to run `gno clean -modcache` from anywhere + rename and use `gnomod.ModCachePath` + tmp `GNOHOME` in main tests (#3083) +da79c846 test(ci): coverpkg=gno.land/... for txtar tests (#3088) +d73b6c6d ci: add go caching (#3091) +7ef606ce fix(gnovm): prevent assignment to non-assignable expressions (#2896) +9129e4e9 fix(gnovm): don't print to stdout by default (#3076) +81a88a29 feat: add p/avl/pager (#2584) +e2e94353 chore: `s/Gno.land/gno.land/g` (#3047) +2173b49d feat(examples): add `r/demo/daoweb` realm (#3074) +9dad8f18 fix(simpledao): reject invalid voting options (#3077) +724ffc99 ci: add a stale bot for PRs (#2804) +f2928f1c test(cmd/gno): prevent nil deref in testMainCaseRun (#3071) +538ebff9 feat: change `gnoweb` to add URL query string to render path (#2876) +367408af chore: fix lint issues from `go vet` (#3069) +9096ef4e fix(tm2): rename methods to avoid conflicts with (un)marshaler interfaces (#3000) +95df7b0c chore: tidy `misc/loop` mod (#3065) +e3995b97 chore: update `tx-archive` in portal loop (#3064) +879041fc feat: add `contribs/gnomigrate` (#3063) +c776e32b feat: support metadata for genesis txs (#2941) +af057801 ci: publish master Docker images without `--snapshot` flag (#3057) +28733546 test(gnovm): indented json on filetests's Events: directives (#3055) +8ec556e6 fix(gnovm): forbid star expression when value is nil (#3053) +e9640ef5 test(gno.land): add unit tests for sdk/vm.vmHandler (#2459) +494976da feat(p/json): remove unnecessary code and optimize (#2939) +850182ca fix(gnovm): forbid star expression when value is not a pointer (#2984) +9786fa36 chore: put replaces on gnolang/gno for all go.mods (#3046) +ed919917 chore: remove install.gnogenesis from root makefile (#3045) +2a2be394 feat: `r/gov/dao` v2 (#2581) +12bd8da5 chore: move `gnoland genesis` to `contribs/gnogenesis` (#3041) +d03581ed ci: don't test verbosely (#3026) +534e6523 docs: fix tm2 broken link (#3034) +2838ad1a ci: add workflow to ensure go.mod files are tidied (#3025) +cfbaff2a ci: benchmark only BenchmarkBenchdata (#3007) +49e718c0 feat: add `p/moul/txlink` + `p/moul/helplink` (#2887) +b849b5a8 ci: run gno test with --print-runtime-metrics (#2979) +603f6d3a chore: relocate tm2/std.MemFile in gnovm/std (#2910) +0b2c67ed fix(gnolang): ensure complete Uverse initialization (#2997) +a478348d chore: remove CODEOWNERS (#3022) +287c22ec fix(gnovm): fix issue with locally re-definition (#3014) +520195e3 feat(stdlibs/std): prohibit getting Banker from a pure package (#2248) +c2aef422 ci: fix goreleaser ci again again (#3010) +4927d4b0 ci: fixup goreleaser workflow, again (#3008) +a2e5c3dd ci: remove unused goreleaser files, ignore chain/ tags (#3004) +f4c4204c feat(gnovm): add 'gno test -print-events' + cleanup machine between tests (#2975) +d9617858 test(stdlibs/io): add additional test (#2898) +5e718378 test(p/uint256): Increase Test Coverage for `uint256` Package (#2931) +247f2c63 feat(ghverify): emit event when user request verification (#2778) +e34a8f7e fix(tm2): enable coin benchmark tests after fixing panic error (#2884) +43dd3f33 feat(tm2): add sdk/params module (#2920) +1a57e81f feat(gnovm): handle loop variables (#2429) +5c876f37 chore(codecov): ignore generated files (#2998) +ec222ec8 feat(examples): add interactive realm `r/stefann/home` (#2918) +4b687129 fix(crypto/keys): in dbKeybase.writeInfo, if replacing a name entry, remove the lookup by the old address (#2685) +47fb3890 chore(deps): bump the actions group across 1 directory with 2 updates (#2995) +11541727 chore: remove vmkeeper.maxcycles (#2993) +8417ca43 chore: remove misc/logos (#2965) +af169ffd fix(ghverify): fix avl tree iteration in `Render` and `GetFeedDefinitions` (#2933) +88a0c4e4 fix(gonative): add constant information when we define a go native value (#2848) +13dfd218 chore(pages): update bounty Ter \ No newline at end of file diff --git a/gnovm/cmd/gno/gno b/gnovm/cmd/gno/gno new file mode 100755 index 00000000000..dbaf75c6f11 Binary files /dev/null and b/gnovm/cmd/gno/gno differ diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index d54b12f6a4f..018023fab12 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -39,6 +39,9 @@ type testCfg struct { printRuntimeMetrics bool printEvents bool withNativeFallback bool + // 새로 두 개의 필드 추가함 + fuzzName string + fuzzIters uint } func newTestCmd(io commands.IO) *commands.Command { @@ -159,6 +162,18 @@ func (c *testCfg) RegisterFlags(fs *flag.FlagSet) { false, "print emitted events", ) + fs.StringVar( + &c.fuzzName, + "fuzz", + "", + "specify fuzz target name (e.g. FuzzXXX) or 'Fuzz' for all fuzz tests", + ) + fs.UintVar( + &c.fuzzIters, + "i", + 50000, + "number of fuzz iterations to run", + ) } func execTest(cfg *testCfg, args []string, io commands.IO) error { @@ -234,16 +249,19 @@ func gnoTestPkg( io commands.IO, ) error { var ( - verbose = cfg.verbose - rootDir = cfg.rootDir - runFlag = cfg.run + verbose = cfg.verbose + rootDir = cfg.rootDir + runFlag = cfg.run + + fuzzName = cfg.fuzzName + fuzzIters = cfg.fuzzIters + printRuntimeMetrics = cfg.printRuntimeMetrics printEvents = cfg.printEvents - - stdin = io.In() - stdout = io.Out() - stderr = io.Err() - errs error + stdin = io.In() + stdout = io.Out() + stderr = io.Err() + errs error ) mode := tests.ImportModeStdlibsOnly @@ -251,7 +269,7 @@ func gnoTestPkg( // XXX: display a warn? mode = tests.ImportModeStdlibsPreferred } - if !verbose { + if !verbose && fuzzName == "" { // TODO: speedup by ignoring if filter is file/*? mockOut := bytes.NewBufferString("") stdout = commands.WriteNopCloser(mockOut) @@ -293,7 +311,8 @@ func gnoTestPkg( stdin, stdout, stderr, mode, ) - if verbose { + //True processing by default when fuzzing + if verbose || fuzzName != "" { testStore.SetLogStoreOps(true) } @@ -306,10 +325,20 @@ func gnoTestPkg( m.Alloc = gno.NewAllocator(maxAllocTx) } m.RunMemPackage(memPkg, true) - err := runTestFiles(m, tfiles, memPkg.Name, verbose, printRuntimeMetrics, printEvents, runFlag, io) - if err != nil { - errs = multierr.Append(errs, err) + + if fuzzName != "" { + err := runFuzzFiles(m, tfiles, memPkg.Name, verbose, fuzzName, fuzzIters, printRuntimeMetrics, printEvents, io) + if err != nil { + errs = multierr.Append(errs, err) + } + + } else { + err := runTestFiles(m, tfiles, memPkg.Name, verbose, printRuntimeMetrics, printEvents, runFlag, io) + if err != nil { + errs = multierr.Append(errs, err) + } } + } // test xxx_test pkg @@ -340,9 +369,17 @@ func gnoTestPkg( memPkg.Path = memPkg.Path + "_test" m.RunMemPackage(memPkg, true) - err := runTestFiles(m, ifiles, testPkgName, verbose, printRuntimeMetrics, printEvents, runFlag, io) - if err != nil { - errs = multierr.Append(errs, err) + if fuzzName != "" { + err := runFuzzFiles(m, tfiles, memPkg.Name, verbose, fuzzName, fuzzIters, printRuntimeMetrics, printEvents, io) + if err != nil { + errs = multierr.Append(errs, err) + } + + } else { + err := runTestFiles(m, tfiles, memPkg.Name, verbose, printRuntimeMetrics, printEvents, runFlag, io) + if err != nil { + errs = multierr.Append(errs, err) + } } } } @@ -439,7 +476,6 @@ func runTestFiles( errs = multierr.Append(fmt.Errorf("panic: %v\nstack:\n%v\ngno machine: %v", r, string(debug.Stack()), m.String()), errs) } }() - testFuncs := &testFuncs{ PackageName: pkgName, Verbose: verbose, @@ -447,24 +483,19 @@ func runTestFiles( } loadTestFuncs(pkgName, testFuncs, files) - // before/after statistics numPackagesBefore := m.Store.NumMemPackages() testmain, err := formatTestmain(testFuncs) if err != nil { log.Fatal(err) } - m.RunFiles(files.Files...) n := gno.MustParseFile("main_test.gno", testmain) m.RunFiles(n) - for _, test := range testFuncs.Tests { // cleanup machine between tests tests.CleanupMachine(m) - testFuncStr := fmt.Sprintf("%q", test.Name) - eval := m.Eval(gno.Call("runtest", testFuncStr)) if printEvents { @@ -518,9 +549,108 @@ func runTestFiles( allocsVal, ) } + } + return errs +} + +func runFuzzFiles( + m *gno.Machine, + files *gno.FileSet, + pkgName string, + verbose bool, + fuzzName string, + fuzzIters uint, + printRuntimeMetrics bool, + printEvents bool, + io commands.IO, +) (errs error) { + defer func() { + if r := recover(); r != nil { + errs = multierr.Append(fmt.Errorf("panic: %v\nstack:\n%v\ngno machine: %v", r, string(debug.Stack()), m.String()), errs) + } + }() + fuzzFuncs := &fuzzFuncs{ + PackageName: pkgName, + Verbose: verbose, + FuzzName: fuzzName, + FuzzIters: fuzzIters} + + loadFuzzFuncs(pkgName, fuzzFuncs, files) + + // before/after statistics + numPackagesBefore := m.Store.NumMemPackages() + + fuzzmain, err := formatFuzzmain(fuzzFuncs) + if err != nil { + log.Fatal(err) + } + m.RunFiles(files.Files...) + n := gno.MustParseFile("main_test.gno", fuzzmain) + m.RunFiles(n) + for _, fuzz := range fuzzFuncs.Fuzzs { + // cleanup machine between tests + tests.CleanupMachine(m) + fuzzFuncStr := fmt.Sprintf("%q", fuzz.Name) + + eval := m.Eval(gno.Call("runfuzz", fuzzFuncStr)) + + if printEvents { + events := m.Context.(*teststd.TestExecContext).EventLogger.Events() + if events != nil { + res, err := json.Marshal(events) + if err != nil { + panic(err) + } + io.ErrPrintfln("EVENTS: %s", string(res)) + } + } + + ret := eval[0].GetString() + if ret == "" { + err := errors.New("failed to execute unit test: %q", fuzz.Name) + errs = multierr.Append(errs, err) + io.ErrPrintfln("--- FAIL: %s [internal gno testing error]", fuzz.Name) + continue + } + + // TODO: replace with amino or send native type? + var rep report + err = json.Unmarshal([]byte(ret), &rep) + if err != nil { + errs = multierr.Append(errs, err) + io.ErrPrintfln("--- FAIL: %s [internal gno testing error]", fuzz.Name) + continue + } + + if rep.Failed { + err := errors.New("failed: %q", fuzz.Name) + errs = multierr.Append(errs, err) + } + + if printRuntimeMetrics { + imports := m.Store.NumMemPackages() - numPackagesBefore - 1 + // XXX: store changes + // XXX: max mem consumption + allocsVal := "n/a" + if m.Alloc != nil { + maxAllocs, allocs := m.Alloc.Status() + allocsVal = fmt.Sprintf("%s(%.2f%%)", + prettySize(allocs), + float64(allocs)/float64(maxAllocs)*100, + ) + } + io.ErrPrintfln("--- runtime: cycle=%s imports=%d allocs=%s", + prettySize(m.Cycles), + imports, + allocsVal, + ) + } + + } return errs + } // mirror of stdlibs/testing.Report @@ -542,6 +672,7 @@ var tests = []testing.InternalTest{ {{end}} } + func runtest(name string) (report string) { for _, test := range tests { if test.Name == name { @@ -552,6 +683,29 @@ func runtest(name string) (report string) { return "" } `)) +var fuzzmainTmpl = template.Must(template.New("fuzzmain").Parse(` +package {{ .PackageName }} + +import ( + "testing" +) + +var fuzzs = []testing.InternalFuzz{ +{{range .Fuzzs}} + {"{{.Name}}", {{.Name}}}, +{{end}} +} + +func runfuzz(name string) (report string) { + for _, fuzz := range fuzzs { + if fuzz.Name == name { + return testing.RunFuzz("{{.FuzzName}}", {{.FuzzIters}},{{.Verbose}}, fuzz) + } + } + panic("no such test: " + name) + return "" +} +`)) type testFuncs struct { Tests []testFunc @@ -559,11 +713,22 @@ type testFuncs struct { Verbose bool RunFlag string } +type fuzzFuncs struct { + Fuzzs []fuzzFunc + PackageName string + Verbose bool + FuzzName string + FuzzIters uint +} type testFunc struct { Package string Name string } +type fuzzFunc struct { + Package string + Name string +} func getPkgNameFromFileset(files *gno.FileSet) string { if len(files.Files) <= 0 { @@ -579,6 +744,13 @@ func formatTestmain(t *testFuncs) (string, error) { } return buf.String(), nil } +func formatFuzzmain(f *fuzzFuncs) (string, error) { + var buf bytes.Buffer + if err := fuzzmainTmpl.Execute(&buf, f); err != nil { + return "", err + } + return buf.String(), nil +} func loadTestFuncs(pkgName string, t *testFuncs, tfiles *gno.FileSet) *testFuncs { for _, tf := range tfiles.Files { @@ -597,6 +769,23 @@ func loadTestFuncs(pkgName string, t *testFuncs, tfiles *gno.FileSet) *testFuncs } return t } +func loadFuzzFuncs(pkgName string, f *fuzzFuncs, tfiles *gno.FileSet) *fuzzFuncs { + for _, ff := range tfiles.Files { + for _, d := range ff.Decls { + if fd, ok := d.(*gno.FuncDecl); ok { + fname := string(fd.Name) + if strings.HasPrefix(fname, "Fuzz") { + tf := fuzzFunc{ + Package: pkgName, + Name: fname, + } + f.Fuzzs = append(f.Fuzzs, tf) + } + } + } + } + return f +} // parseMemPackageTests is copied from gno.ParseMemPackageTests // for except to _filetest.gno diff --git a/gnovm/stdlibs/generated.go b/gnovm/stdlibs/generated.go index a2d82b0bc60..9bf2c729ffb 100644 --- a/gnovm/stdlibs/generated.go +++ b/gnovm/stdlibs/generated.go @@ -880,6 +880,7 @@ var nativeFuncs = [...]NativeFunc{ )) }, }, + { "time", "now", diff --git a/gnovm/stdlibs/testing/crash_logger.gno b/gnovm/stdlibs/testing/crash_logger.gno new file mode 100644 index 00000000000..10e201bd11f --- /dev/null +++ b/gnovm/stdlibs/testing/crash_logger.gno @@ -0,0 +1,210 @@ +package testing + +import ( + "errors" + "strconv" + "strings" + "time" +) + +type TestResult struct { + PanicOccurred bool + PanicMessage string + Error error +} + +type Crash_Case struct { + HashNumber uint + Input string + RawContent []interface{} + InputCount uint + IsPanic bool + PanicMessage string + ErrorMsg string + Timestamp time.Time +} + +type Crash_Logger struct { + data []Crash_Case +} + +func New_Crash_Logger() *Crash_Logger { + return &Crash_Logger{ + data: make([]Crash_Case, 0), + } +} + +func (log *Crash_Logger) AddCase(seed Seed, r TestResult) { + var crashCase Crash_Case + if r.Error == nil { + crashCase = Crash_Case{ + HashNumber: uint(seed.HashNumber), + Input: ContentToString(seed.Content), + RawContent: seed.Content, + InputCount: seed.Id, + IsPanic: r.PanicOccurred, + PanicMessage: r.PanicMessage, + ErrorMsg: "", + Timestamp: time.Now(), + } + } else { + crashCase = Crash_Case{ + HashNumber: uint(seed.HashNumber), + Input: ContentToString(seed.Content), + RawContent: seed.Content, + InputCount: seed.Id, + IsPanic: r.PanicOccurred, + PanicMessage: r.PanicMessage, + ErrorMsg: strings.TrimSpace(r.Error.Error()), + Timestamp: time.Now(), + } + } + log.data = append(log.data, crashCase) +} + +func (log *Crash_Logger) GetCase(index int) (Crash_Case, error) { + if index < 0 || index >= len(log.data) { + return Crash_Case{}, errors.New("index out of bounds") + } + return log.data[index], nil +} + +func (log *Crash_Logger) RemoveCase(index int) error { + if index < 0 || index >= len(log.data) { + return errors.New("index out of bounds") + } + log.data = append(log.data[:index], log.data[index+1:]...) + return nil +} + +func (log *Crash_Logger) ListCases() []Crash_Case { + return log.data +} + +func (log *Crash_Logger) Size() int { + return len(log.data) +} + +func (log *Crash_Logger) ClearLog() { + log.data = make([]Crash_Case, 0) +} + +type KindInfo struct { + HashNumber uint + RepresentativeError string + RepresentativeInput string + IsPanic bool + Count int +} + +func (log *Crash_Logger) Kind() []KindInfo { + lookup := make(map[uint]*KindInfo) + + for _, c := range log.data { + ki, exists := lookup[c.HashNumber] + if !exists { + + repMsg := "" + + if c.IsPanic { + repMsg = c.PanicMessage + } else { + repMsg = c.ErrorMsg + } + lookup[c.HashNumber] = &KindInfo{ + HashNumber: c.HashNumber, + RepresentativeError: repMsg, + RepresentativeInput: c.Input, + IsPanic: c.IsPanic, + Count: 1, + } + } else { + ki.Count++ + } + } + + result := make([]KindInfo, 0, len(lookup)) + for _, ki := range lookup { + result = append(result, *ki) + } + return result +} + +func (log *Crash_Logger) Samples_of_AllKinds() map[uint][]Crash_Case { + lookup := make(map[uint][]Crash_Case) + for _, c := range log.data { + lookup[c.HashNumber] = append(lookup[c.HashNumber], c) + } + + for hn, cases := range lookup { + if len(cases) > 3 { + cases = cases[:3] + } + lookup[hn] = cases + } + return lookup +} + +type CrashSummary struct { + TotalCrashes int + HashNumbers []uint + MostFrequentHN uint + MostFrequentCount int + PanicCount int + ErrorCount int +} + +func (log *Crash_Logger) Summary() CrashSummary { + sm := CrashSummary{} + sm.TotalCrashes = len(log.data) + lookup := make(map[uint]int) + for _, c := range log.data { + lookup[c.HashNumber]++ + if c.IsPanic { + sm.PanicCount++ + } else { + sm.ErrorCount++ + } + } + for k := range lookup { + sm.HashNumbers = append(sm.HashNumbers, k) + } + + for hn, cnt := range lookup { + if cnt > sm.MostFrequentCount { + sm.MostFrequentCount = cnt + sm.MostFrequentHN = hn + } + } + return sm +} + +func PrintSummary(sum CrashSummary) { + println("----- Crash Log Summary -----") + println("TotalCrashes:", sum.TotalCrashes) + println("HashNumbers:", sliceToString(sum.HashNumbers)) + println("MostFrequentHashNumber:", sum.MostFrequentHN, "(occurs", sum.MostFrequentCount, "times)") + println("PanicCount:", sum.PanicCount) + println("ErrorCount:", sum.ErrorCount) + println("--------------------------------") +} + +func PrintKinds(kinds []KindInfo) { + if len(kinds) == 0 { + println("No crash kinds found.") + return + } + + println("----- Crash Kinds -----") + for _, k := range kinds { + println("HashNumber:", k.HashNumber, + ", IsPanic:", k.IsPanic, + ", Count:", k.Count, + ", RepresentativeError:", k.RepresentativeError) + + inputStr := k.RepresentativeInput + println(" Printed Input:", inputStr) + println(" In machine(Escaped edge bytes) Input:", TransForHuman(inputStr)) + println("-----------------------") + } +} diff --git a/gnovm/stdlibs/testing/fuzz.gno b/gnovm/stdlibs/testing/fuzz.gno index 74d64c231b6..b6014678637 100644 --- a/gnovm/stdlibs/testing/fuzz.gno +++ b/gnovm/stdlibs/testing/fuzz.gno @@ -1,284 +1,548 @@ package testing -import "strings" - -type Fuzzer interface { - InsertDeleteMutate(p float64) Fuzzer - Mutate() Fuzzer - String() string -} - -type StringFuzzer struct { - Value string - f *F +import ( + "errors" + "fmt" + "os" + "strconv" + "strings" + "unicode/utf8" +) + +type ( + Runner func(*T, ...interface{}) + F struct { + fsm *StateMachine + fhm *HashMachine + corpus []Seed + msgs []string // Stores log messages for reporting. + failed bool // Indicates whether the fuzzing has encountered a failure. + ff Runner + isFuzzed bool + seedCount uint + // for cmd test + output []byte + verbose bool // Stores log messages for reporting. + trials uint // Number of iterations to run the fuzzing process. + dur string + name string + } +) + +func New_F(verbose bool, trials uint) *F { + println("creating fuzzing enviroment...") + new_fsm := New_StateMachine(trials, 0) + new_fsm.verbose = verbose + new_fhm := New_HashMachine() + return &F{ + verbose: verbose, + fsm: new_fsm, + fhm: new_fhm, + isFuzzed: false, + seedCount: 0, + trials: trials, + } } -func NewStringFuzzer(value string) *StringFuzzer { - return &StringFuzzer{Value: value} +func typeof(arg interface{}) (SupportedType, error) { + switch v := arg.(type) { + case []byte: + return Byte_Array, nil + case string: + return String, nil + case bool: + return Bool, nil + case byte: + return Byte, nil + case rune: + return Rune, nil + case float32: + return Float32, nil + case float64: + return Float64, nil + case int: + return Int, nil + case int8: + return Int8, nil + case int16: + return Int16, nil + // deduplication because int32 and rune are of the same type + // case int32: + // return Int32, nil + case int64: + return Int64, nil + case uint: + return Uint, nil + // deduplication + // case uint8: + // return Uint8, nil + case uint16: + return Uint16, nil + case uint32: + return Uint32, nil + case uint64: + return Uint64, nil + default: + println("unsupported type:", v) + return "", errors.New("unsupported type:") + } } -// Mutate changes a StringFuzzer's value by replacing a random character -// with a random ASCII character. -func (sf *StringFuzzer) Mutate() Fuzzer { - runes := []rune(sf.Value) - if len(runes) == 0 { - return sf +func (f *F) Add(args ...interface{}) { + if f.isFuzzed { + panic("Add after Fuzz") + } + var values []interface{} + var types []SupportedType + if len(args) == 0 { + panic("no-argument is denied") + } + for i := range args { + t, e := typeof(args[i]) + if e != nil { + panic("not supported type") + } + values = append(values, args[i]) + types = append(types, t) } - index := randRange(0, len(runes)-1) - runes[index] = randomASCIIChar() - - return NewStringFuzzer(string(runes)) -} - -func (sf *StringFuzzer) InsertDeleteMutate(p float64) Fuzzer { - value := InsertDelete(sf.Value, p) - return NewStringFuzzer(value) + if f.fsm.SeedType == nil { + f.fsm.SeedType = types + } else { + if !isSliceEqual(f.fsm.SeedType, types) { + panic("added arguments not equal together") + } + } + f.seedCount++ + f.corpus = append(f.corpus, Seed{ + Pid: f.seedCount, + Id: f.seedCount, + Gen: 1, IsCoordinated: false, Content: values, + }) } -func (sf *StringFuzzer) Fuzz() string { - if GenerateRandomBool(0.2) { - return InsertDelete(sf.Value, 0.1) +func (f *F) Fuzz(run Runner) { + println("----------------------------- Fuzz Start --------------------------------") + if !f.isFuzzed { + f.isFuzzed = true + } else { + panic("fuzz called more than once") } - rs := []rune(sf.Value) - lrs := len(rs) + // format machine + f.ff = run + for i, corp := range f.corpus { + println("Adding corpus", i+1) + corp = f.simulateFF(corp) + if f.failed { + f.handleFail() + return + } - if lrs == 0 { - return sf.Value - } + hashNumber := corp.HashNumber + f.fsm.init_hashnumber = append(f.fsm.init_hashnumber, hashNumber) + endInfo := f.fsm.CoordinateSeed(corp) + if endInfo.Complete_Trials { + f.reportF() + return + } + if endInfo.MAXed_CAPACITY { - index := randRange(0, lrs-1) - rs[index] = randomASCIIChar() + f.migrateMachines() + continue + } - return string(rs) -} + } -func (sf *StringFuzzer) String() string { - return sf.Value -} + string_byte_candidates := []int{} + for i, t := range f.fsm.SeedType { + if t == String || t == Byte_Array { + string_byte_candidates = append(string_byte_candidates, i) + } + } -func randomASCIIChar() rune { - r := int(randRange(32, 126)) + f.fsm.string_byte_candidates = string_byte_candidates + println("Run trials...") - return rune(r) -} + // format by init seeds + for i := 0; i < len(f.corpus); i++ { + hn := f.fsm.init_hashnumber[i] + initSeed := []Seed{f.fsm.PopInitSeedByHN(hn)} + is_end := f.updateMachines(initSeed) + if is_end { + return + } + } -// Individual represents a single individual in the population. -type Individual struct { - Fuzzer Fuzzer - Fitness int + for { + parentSeeds := f.fsm.PopSeeds() + is_end := f.updateMachines(parentSeeds) + if is_end { + return + } + } } -func NewIndividual(fuzzer Fuzzer) *Individual { - return &Individual{Fuzzer: fuzzer} +type abstractSeedInfo struct { + seeds []Seed + abstractNumber uint } -func (ind *Individual) calculateFitness() { - ind.Fitness = len(ind.Fuzzer.String()) +func (f *F) handleEndInfo(endInfo EndInfo, parentSeeds []Seed) bool { + if endInfo.Complete_Trials { + f.reportF() + return true + } + if endInfo.MAXed_CAPACITY { + for _, p := range parentSeeds { + f.fsm.CoordinateSeed(p) + } + f.migrateMachines() + return false + } + return false } -// Selection selects individuals from the population based on their fitness. -// -// Use roulette wheel selection to select individuals from the population. -// ref: https://en.wikipedia.org/wiki/Fitness_proportionate_selection -func Selection(population []*Individual) []*Individual { - totalFitness := calculateTotalFitness(population) - selected := make([]*Individual, len(population)) +func (f *F) updateMachines(parentSeeds []Seed) bool { + abstractSeedMap := make(map[HashNumber]abstractSeedInfo) + childSeeds := Evolve(parentSeeds, &f.seedCount, f.fsm.string_byte_candidates) + // println("childlen", len(childSeeds)) - for i := range selected { - selected[i] = selectIndividual(population, totalFitness) + for _, child := range childSeeds { + child = f.simulateFF(child) + if f.failed { + f.handleFail() + return true + } + hn := child.HashNumber + equal_hn_seeds := abstractSeedMap[hn].seeds + abstract_number := abstractSeedMap[hn].abstractNumber + if len(equal_hn_seeds) < 2 { + equal_hn_seeds = append(equal_hn_seeds, child) + abstract_number++ + } else { + abstract_number++ + } + absSeedInfo := abstractSeedInfo{ + seeds: equal_hn_seeds, + abstractNumber: abstract_number, + } + abstractSeedMap[hn] = absSeedInfo } + // println("len!", len(abstractSeedMap)) + + for hn, absSeedInfo := range abstractSeedMap { + seeds := absSeedInfo.seeds + if len(seeds) == 1 { + // println("hashNumber", hn, ": 1") + concreteEndInfo := f.fsm.CoordinateSeed(seeds[0]) + flag := f.handleEndInfo(concreteEndInfo, parentSeeds) + if flag { + return flag + } + } else { + absNumber := absSeedInfo.abstractNumber - 2 + // println("hashNumber", hn, ":", absNumber+2) + absEndInfo := f.fsm.CoordinateAbstraction(hn, absNumber) + flag := f.handleEndInfo(absEndInfo, parentSeeds) + if flag { + return flag + } + concreteEndInfo1 := f.fsm.CoordinateSeed(seeds[0]) + flag = f.handleEndInfo(concreteEndInfo1, parentSeeds) + if flag { + return flag + } + concreteEndInfo2 := f.fsm.CoordinateSeed(seeds[1]) + flag = f.handleEndInfo(concreteEndInfo2, parentSeeds) + if flag { + return flag + } + } - return selected + } + // println("iters!") + for _, p := range parentSeeds { + f.fsm.CoordinateSeed(p) + } + return false } -func calculateTotalFitness(population []*Individual) int { - totalFitness := 0 +func (f *F) handleFail() { + f.Fail() + println("----------------------------- FAIL --------------------------------") + log := f.fsm.Crash_Logger.data + crashCase := log[len(log)-1] + println("found failing input", TransForHuman(crashCase.Input), "at", f.fsm.Input_count, "trials") + println("start minimazing failing input") + f.minimazeAndLogInput(crashCase.RawContent) + log = f.fsm.Crash_Logger.data + minimaizedCase := log[len(log)-1] + println(CrashCaseToString(minimaizedCase)) + hn := minimaizedCase.HashNumber + coverage := f.fhm.HashNumber2Coverage(hn) + + println("----------------------------- Trace --------------------------------") + println(CoverageToString(coverage)) + println("--------------------------------------------------------------------") + return +} - for _, ind := range population { - totalFitness += ind.Fitness +func (f *F) migrateMachines() { + println("Machine capacity is full. Start migration...") + // Abstract existing state_machine + summarized_seeds := f.fsm.Summarize() + old_hashNumbers := []HashNumber{} + for _, seed := range summarized_seeds { + old_hashNumbers = append(old_hashNumbers, seed.HashNumber) } - - return totalFitness + coverages_of_seeds := []Coverage{} + for _, hn := range old_hashNumbers { + coverages_of_seeds = append(coverages_of_seeds, f.fhm.HashNumber2Coverage(uint(hn))) + } + // Sort by re-registering existing seed coverage to the new hash machine + // Return the value to the seed afterwards + // The hash number of the seed is 0,1,2... and also registers 0,1,2 and coverage on the hash machine + f.fhm = New_HashMachine() + for i, cov := range coverages_of_seeds { + summarized_seeds[i].HashNumber = f.fhm.RegisterCoverage2HashNumber(cov) + } + // Create and relocate a new State_machine + prev_inputCount := f.fsm.Input_count + substracted_inputCount := int(prev_inputCount) - len(summarized_seeds) + f.fsm = New_StateMachine(f.trials, uint(substracted_inputCount)) + f.fsm.verbose = f.verbose + for _, seed := range summarized_seeds { + f.fsm.CoordinateSeed(seed) + } + println("Migration completed. Resume fuzzing...") } -func selectIndividual(population []*Individual, totalFitness int) *Individual { - pick := randRange(0, totalFitness-1) - sum := 0 - - for _, ind := range population { - sum += ind.Fitness - if uint64(sum) > uint64(pick) { - return ind +func (f *F) minimazeAndLogInput(seedContent []interface{}) { + minimazalbeIdXs := []int{} + for i, t := range f.fsm.SeedType { + if t == Byte_Array { + minimazalbeIdXs = append(minimazalbeIdXs, i) + } else if t == String { + minimazalbeIdXs = append(minimazalbeIdXs, i) } } - - return nil -} - -// Crossover takes two parents and creates two children by combining their genetic material. -// -// The pivot point is chosen randomly from the length of the shortest parent. after the pivot point selected, -// the genetic material of the two parents is swapped to create the two children. -func Crossover(parent1, parent2 *Individual) (*Individual, *Individual) { - p1Runes := []rune(parent1.Fuzzer.String()) - p2Runes := []rune(parent2.Fuzzer.String()) - - p1Len := len(p1Runes) - p2Len := len(p2Runes) - - point := 0 - if p1Len >= p2Len { - point = int(randRange(0, p2Len-1)) - } else { - point = int(randRange(0, p1Len-1)) + if len(minimazalbeIdXs) < 1 { + return } - - child1 := append(append([]rune{}, p1Runes[:point]...), p2Runes[point:]...) - child2 := append(append([]rune{}, p2Runes[:point]...), p1Runes[point:]...) - - updatedIdv1 := NewIndividual(NewStringFuzzer(string(child1))) - updatedIdv2 := NewIndividual(NewStringFuzzer(string(child2))) - - return updatedIdv1, updatedIdv2 + sample := seedContent + content := make([]interface{}, len(sample)) + for i, v := range sample { + content[i] = v + } + // minimaze by progressive, preserveness traits of error + for { + progressed, isProgressed, occeredPoint := f.checkProgress(content, minimazalbeIdXs) + if !isProgressed { + break + } + content = progressed + minimazalbeIdXs = minimazalbeIdXs[occeredPoint:] + } + sampleSeed := Seed{ + Content: content, + } + // re confirm error + f.simulateFF(sampleSeed) + println("minimaized input") } -func (ind *Individual) Mutate() { - ind.Fuzzer = ind.Fuzzer.Mutate() +func (f *F) reportF() { + println("----------------------------- PASS --------------------------------") + println("Complete", f.trials, "Trials") + println("Found", (uint(f.fhm.hashNumber_counter.counter) + uint(1)), "coverage") } -// InsertDelete randomly inserts or deletes a character from a string. -func InsertDelete(s string, p float64) string { - rr := []rune(s) - l := len(rr) - - // Insert - if GenerateRandomBool(p) { - pos := randRange(0, l-1) - rr = append(rr, 0) - - copy(rr[pos+1:], rr[pos:]) - - char := randomASCIIChar() - rr[pos] = char - } else { - if l == 0 { - return s +func (f *F) simulateFF(seed Seed) Seed { + coverage, err, isPanic, panicMsg := monitor(f.ff, seed.Content) + + // seed.hn computation + // This completes the status change of the seed before logging + hashNumber := f.fhm.RegisterCoverage2HashNumber(coverage) + seed.HashNumber = hashNumber + if isPanic { + tr := TestResult{ + PanicOccurred: true, + PanicMessage: panicMsg, + Error: err, } - - pos := randRange(0, l-1) - rr = append(rr[:pos], rr[pos+1:]...) + f.fsm.Crash_Logger.AddCase(seed, tr) + f.Fail() } - - return string(rr) + if err != nil { + tr := TestResult{ + PanicOccurred: false, + PanicMessage: "", + Error: err, + } + f.fsm.Crash_Logger.AddCase(seed, tr) + f.Fail() + } + return seed } -type F struct { - corpus []string - failed bool // Indicates whether the fuzzing has encountered a failure. - msgs []string // Stores log messages for reporting. - iters int // Number of iterations to run the fuzzing process. TODO: CLI flag to set this. +// Fail marks the function as having failed bur continue execution. +func (f *F) Fail() { + f.failed = true } -// Runner is a type for the target function to fuzz. -type Runner func(*T, ...interface{}) - -// Fuzz applies the fuzzing process to the target function. -func (f *F) Fuzz(run Runner, iter int) { - f.evolve(iter) - - for _, input := range f.corpus { - args := make([]interface{}, len(f.corpus)) - for i := range args { - args[i] = input - } +// Fatal is equivalent to Log followed by FailNow. +// It logs the message and marks the fuzzing as failed. +func (f *F) Fatal(args ...interface{}) { + var sb strings.Builder - run(nil, args...) + for _, arg := range args { + sb.WriteString(arg.(string)) } + + f.msgs = append(f.msgs, sb.String()) + f.Fail() } -// Add adds test values to initialize the corpus. -func (f *F) Add(values ...interface{}) []Fuzzer { - fuzzers := make([]Fuzzer, len(values)) +func (f *F) checkProgress(content []interface{}, minimazalbeIdXs []int) ([]interface{}, bool, int) { + changed := false + for _, idx := range minimazalbeIdXs { + sOrb := content[idx] - for i, v := range values { - str, ok := v.(string) - if !ok { - continue + switch v := sOrb.(type) { + case string: + if len(v) < 1 { + continue + } + for i := 0; i < len(v); i++ { + b := []byte(v) + candidate := append(b[:i], b[i+1:]...) + + tester := make([]interface{}, len(content)) + for i, v := range content { + tester[i] = v + } + tester[idx] = string(candidate) + if f.checkPreserve(tester).errorIsPreserved { + + changed = true + return tester, changed, idx + } + } + case []byte: + if len(v) < 1 { + continue + } + for i := 0; i < len(v); i++ { + b := []byte(v) + candidate := append(b[:i], b[i+1:]...) + tester := make([]interface{}, len(content)) + for i, v := range content { + tester[i] = v + } + tester[idx] = []byte(candidate) + if f.checkPreserve(tester).errorIsPreserved { + changed = true + return tester, changed, idx + } + } + default: + panic("internal logic error") } - f.corpus = append(f.corpus, str) - fuzzers[i] = &StringFuzzer{Value: str} } - - return fuzzers + return content, changed, 0 } -func (f *F) evolve(generations int) { - population := make([]*Individual, len(f.corpus)) - for i, c := range f.corpus { - population[i] = &Individual{Fuzzer: &StringFuzzer{Value: c, f: f}} - } +type PreserveFailing struct { + errorIsPreserved bool + coverage Coverage + err error + isPanic bool + panicMsg string + hashNumber HashNumber +} - for _, ind := range population { - ind.calculateFitness() +func (f *F) checkPreserve(content []interface{}) PreserveFailing { + coverage, err, isPanic, panicMsg := monitor(f.ff, content) + hashNumber := f.fhm.RegisterCoverage2HashNumber(coverage) + if isPanic { + return PreserveFailing{ + errorIsPreserved: true, + coverage: coverage, + err: err, + isPanic: true, + panicMsg: panicMsg, + hashNumber: hashNumber, + } } - - for gen := 0; gen < generations; gen++ { - population = Selection(population) - newPopulation := make([]*Individual, 0, len(population)) - - for i := 0; i < len(population); i += 2 { - if i+1 < len(population) { - child1, child2 := Crossover(population[i], population[i+1]) - newPopulation = append(newPopulation, child1, child2) - continue - } - - newPopulation = append(newPopulation, population[i]) + if err != nil { + return PreserveFailing{ + errorIsPreserved: true, + coverage: coverage, + err: err, + isPanic: false, + panicMsg: "", + hashNumber: hashNumber, } + } + return PreserveFailing{ + errorIsPreserved: false, + } +} - var bestFitness int - - for _, ind := range newPopulation { - if GenerateRandomBool(0.2) { - ind.Mutate() +// TODO: Make sure to revise coverage here!!! +// TODO: I've hard-coded coverage according to the test results here. This will be corrected later!! +func monitor(run Runner, content []interface{}) (coverage Coverage, err error, isPanic bool, panicMsg string) { + isPanic = false + panicMsg = "" + err = nil + coverage = Coverage{} + defer func() { + if r := recover(); r != nil { + t := NewT("fuzzing") + coverage = Get_Coverage_of_runner(t, content) + isPanic = true + if err, ok := r.(error); ok { + panicMsg = err.Error() + return } + if s, ok := r.(string); ok { - if GenerateRandomBool(0.1) { - ind.Fuzzer = ind.Fuzzer.InsertDeleteMutate(0.3) + panicMsg = s + return } - ind.calculateFitness() - - if ind.Fitness > bestFitness { - bestFitness = ind.Fitness - } + panicMsg = "unknown panic" } + }() + t := NewT("fuzzing") - population = newPopulation + // Ensuring the immutability of content + copied := make([]interface{}, len(content)) + for i, v := range content { + copied[i] = v } - - f.corpus = make([]string, len(population)) - for i, ind := range population { - f.corpus[i] = ind.Fuzzer.String() + run(t, copied...) + info := t.GetResult() + if info.Failed { + err = errors.New(string(info.Output)) } -} + // TODO: Modifying this function to get real coverage + // TODO: It's just pshedo-covrage of some function + coverage = Get_Coverage_of_runner(t, content) -// Fail marks the function as having failed bur continue execution. -func (f *F) Fail() { - f.failed = true + // println(string(t.output)) + return coverage, err, isPanic, panicMsg } -// Fatal is equivalent to Log followed by FailNow. -// It logs the message and marks the fuzzing as failed. -func (f *F) Fatal(args ...interface{}) { - var sb strings.Builder - - for _, arg := range args { - sb.WriteString(arg.(string)) +func isSliceEqual(a, b []SupportedType) bool { + if len(a) != len(b) { + return false } - - f.msgs = append(f.msgs, sb.String()) - f.Fail() + for i := range a { + if a[i] != b[i] { + return false + } + } + return true } diff --git a/gnovm/stdlibs/testing/fuzz_test.gno b/gnovm/stdlibs/testing/fuzz_test.gno deleted file mode 100644 index 307148c09b3..00000000000 --- a/gnovm/stdlibs/testing/fuzz_test.gno +++ /dev/null @@ -1,168 +0,0 @@ -package testing - -import "strings" - -func TestMutate(t *T) { - originalValue := "Hello" - fuzzer := StringFuzzer{Value: originalValue} - - newFuzzer := fuzzer.Mutate().(*StringFuzzer) - - if newFuzzer.Value == originalValue { - t.Errorf("Mutate did not change the string: got %v, want different from %v", newFuzzer.Value, originalValue) - } - - if len(newFuzzer.Value) != len(originalValue) { - t.Errorf("Mutated string has different length: got %s (len=%v), want %s (len=%v)", newFuzzer.Value, len(newFuzzer.Value), originalValue, len(originalValue)) - } -} - -func TestSelection(t *T) { - tests := []struct { - name string - population []*Individual - }{ - { - name: "Empty population", - population: []*Individual{}, - }, - { - name: "Uniform fitness", - population: []*Individual{ - {Fitness: 10}, - {Fitness: 10}, - {Fitness: 10}, - }, - }, - { - name: "Different fitness", - population: []*Individual{ - {Fitness: 5}, - {Fitness: 15}, - {Fitness: 10}, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *T) { - selected := Selection(tc.population) - if len(selected) != len(tc.population) { - t.Errorf("Expected selected length to be %d, got %d", len(tc.population), len(selected)) - } - }) - } -} - -func TestCrossover(t *T) { - parent1 := NewIndividual(&StringFuzzer{Value: "foobar"}) - parent2 := NewIndividual(&StringFuzzer{Value: "bazbiz"}) - - var child1, child2 *Individual - for i := 0; i < 100; i++ { - child1, child2 = Crossover(parent1, parent2) - } - - if child1.Fuzzer.String() == "foobar" || child2.Fuzzer.String() == "bazbiz" { - t.Errorf("Crossover did not modify children correctly, got %s and %s", child1.Fuzzer.String(), child2.Fuzzer.String()) - } -} - -func Test_StringManipulation(t *T) { - f := &F{ - corpus: []string{"hello", "world", "foo", "bar"}, - } - - f.evolve(30) - - if len(f.corpus) != 4 { - t.Fatalf("corpus length is %d, want 4", len(f.corpus)) - } - - for i, c := range f.corpus { - if c == "" { - t.Fatalf("corpus[%d] is empty", i) - } - - if len(c) < 3 { - t.Fatalf("corpus[%d] is too short: %s", i, c) - } - - if f.corpus[0] == "hello" { - t.Fatalf("corpus[0] is still the same: %s", f.corpus[0]) - } - - if f.corpus[1] == "world" { - t.Fatalf("corpus[1] is still the same: %s", f.corpus[1]) - } - - if f.corpus[2] == "foo" { - t.Fatalf("corpus[2] is still the same: %s", f.corpus[2]) - } - - if f.corpus[3] == "bar" { - t.Fatalf("corpus[3] is still the same: %s", f.corpus[3]) - } - - } -} - -func TestFuzz(t *T) { - f := F{} - f.Add("hello", "world", "foo") - f.Fuzz(func(t *T, inputs ...interface{}) { - for _, input := range inputs { - strInput, ok := input.(string) - if !ok { - t.Errorf("Type mismatch, expected a string but got %T", input) - continue - } - - words := strings.Fields(strInput) - if len(words) == 0 { - t.Errorf("Expected non-empty input") - } - } - }, 15) - - if len(f.corpus) == 0 { - t.Fatalf("Fuzzing corpus is empty after testing") - } - - if len(f.corpus) > 3 { - t.Fatalf("Fuzzing corpus has more than 3 elements: %v", f.corpus) - } - - for _, c := range f.corpus { - if c == "hello" || c == "world" || c == "foo" { - t.Fatalf("Fuzzing corpus still contains the original elements: %v", f.corpus) - } - } -} - -func TestF_Fail(t *T) { - f := F{} - f.Fail() - - if !f.failed { - t.Errorf("Fail did not set the failed flag.") - } -} - -func TestF_Fatal(t *T) { - f := F{} - testMessage := "test failure message" - f.Fatal(testMessage) - - if !f.failed { - t.Errorf("Fatal did not set the failed flag.") - } - - if len(f.msgs) != 1 { - t.Fatalf("Fatal did not set the message correctly: got %v, want %v", f.msgs, testMessage) - } - - if !strings.Contains(f.msgs[0], testMessage) { - t.Errorf("Fatal did not set the message correctly: got %v, want %v", f.msgs[0], testMessage) - } -} diff --git a/gnovm/stdlibs/testing/genom_alg.gno b/gnovm/stdlibs/testing/genom_alg.gno new file mode 100644 index 00000000000..9b930bb75d6 --- /dev/null +++ b/gnovm/stdlibs/testing/genom_alg.gno @@ -0,0 +1,1135 @@ +package testing + +import ( + "bytes" + "errors" + "math" + "strconv" + "time" + "unicode/utf8" +) + +func Mutate(seed Seed, mut_range int) Seed { + if len(seed.Content) == 0 { + panic("mutate logic error: content's len==0") + } + index := 0 + if len(seed.Content) > 1 { + index = int(RandRange(0, int64(len(seed.Content)))) + } + + selected := seed.Content[index] + switch v := selected.(type) { + case int, int8, int16, int32, int64: + for i := 0; i < mut_range; i++ { + seed.Content[index] = randomInt_from(v) + } + case uint, uint8, uint16, uint32, uint64: + for i := 0; i < mut_range; i++ { + seed.Content[index] = randomUint_from(v) + } + case float32, float64: + for i := 0; i < mut_range; i++ { + seed.Content[index] = randomFloat_from(v) + } + case bool: + seed.Content[index] = randomBool() + // Cancellation due to value set issue + // case string: + // runes := []rune(v) + // if len(runes) > 0 { + // runeIndex := RandRange(0, int64(len(runes))) + // runes[runeIndex] = randomRune_from(runes[runeIndex]) + // } + // var new_str string = string(runes) + // seed.Content[index] = new_str + case string: + bytes := []byte(v) + if len(bytes) > 0 { + for i := 0; i < mut_range; i++ { + byteIndex := RandRange(0, int64(len(bytes))) + bytes[byteIndex] = randomByte_from(bytes[byteIndex]) + } + } + var new_str string = string(bytes) + seed.Content[index] = new_str + + case []byte: + bytes := []byte(v) + if len(bytes) > 0 { + for i := 0; i < mut_range; i++ { + byteIndex := RandRange(0, int64(len(bytes))) + bytes[byteIndex] = randomByte_from(bytes[byteIndex]) + } + } + var new_byt []byte = []byte(bytes) + seed.Content[index] = new_byt + default: + panic("not supported type") + } + + return seed +} + +func InsertDelete(seed Seed, p float64, mut_range int, string_byte_candidates []int) Seed { + if len(string_byte_candidates) == 0 { + return seed + } + + index := 0 + if len(string_byte_candidates) > 0 { + + selected_field_idx := RandRange(0, int64(len(string_byte_candidates))) + index = string_byte_candidates[selected_field_idx] + } + + selected := seed.Content[index] + + switch v := selected.(type) { + case []byte: + bb := []byte(v) + for i := 0; i < mut_range; i++ { + l := len(bb) + // Insert + if GenerateRandomBool(p) { + if l < 1 { + var b byte = ' ' + bb = []byte{randomByte_from(b)} + } else { + + sample := bb[RandRange(0, int64(l))] + bt := randomByte_from(sample) + + pos := RandRange(0, int64(l)) + + bb = append(bb, 0) + + copy(bb[pos+1:], bb[pos:]) + + bb[pos] = bt + } + } else { + // Del + if l == 0 { + return seed + } + pos := RandRange(0, int64(l)) + bb = append(bb[:pos], bb[pos+1:]...) + } + + } + var new_byt []byte = bb + seed.Content[index] = new_byt + case string: + bb := []byte(v) + for i := 0; i < mut_range; i++ { + l := len(bb) + // Insert + if GenerateRandomBool(p) { + if l < 1 { + var b byte = ' ' + bb = []byte{randomByte_from(b)} + } else { + + sample := bb[RandRange(0, int64(l))] + bt := randomByte_from(sample) + + pos := RandRange(0, int64(l)) + + bb = append(bb, 0) + + copy(bb[pos+1:], bb[pos:]) + + bb[pos] = bt + } + } else { + if l != 0 { + pos := RandRange(0, int64(l)) + bb = append(bb[:pos], bb[pos+1:]...) + } + } + + } + var new_str string = string(bb) + seed.Content[index] = new_str + default: + println("maybe some nil string error:", v) + panic("internal logic error") + } + return seed +} + +// I deleted the existing fit, fitness. +// As I tried to increase the speed by integrating into AFL, I decided that it was faster to manage it with just queue, stack, and unique linked list. +// (Something gets uncomfortable if you follow the afl logic I've seen and maintain that fitness management.) +// Fitness and selection logic are replaced. + +// Modified existing mating logic. +// I adjusted the number according to gen to solve the sticking problem. +// Changed to multi-intersection logic. +func TwoPointCrossover(parent1, parent2 Seed, seedCount *uint) (Seed, Seed) { + // 깊은 복사를 위해 새로운 슬라이스 생성 + content1 := make([]interface{}, len(parent1.Content)) + for i, v := range parent1.Content { + content1[i] = v // 안전하게 string으로 캐스팅 + } + content2 := make([]interface{}, len(parent2.Content)) + for i, v := range parent2.Content { + content2[i] = v + } + + for i := 0; i < len(parent1.Content); i++ { + switch v1 := content1[i].(type) { + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64: + content1[i], content2[i] = factorization_crossover(v1, content2[i]) + case bool: + content1[i] = v1 + content2[i] = content2[i] + + case []byte: + byt1 := v1 + byt2, ok := parent2.Content[i].([]byte) + if !ok { + panic("type not equal") + } + p1Bytes := []byte(byt1) + p2Bytes := []byte(byt2) + p1Len := len(p1Bytes) + p2Len := len(p2Bytes) + minLen := p1Len + if p2Len < p1Len { + minLen = p2Len + } + if minLen == 0 { + maxLen := p1Len + m := 1 + if p2Len > p1Len { + m = 2 + maxLen = p2Len + } + if maxLen < 1 { + s := ' ' + bb := byte(s) + content1[i] = []byte([]byte{randomByte_from(bb)}) + content2[i] = []byte([]byte{randomByte_from(bb)}) + continue + } else { + if m == 1 { + content2[i] = content1[i] + } else { + content1[i] = content2[i] + } + continue + } + } + + point1 := RandRange(0, int64(minLen)) + point2 := RandRange(0, int64(minLen)) + + if point1 > point2 { + point1, point2 = point2, point1 + } + + crossed_byt1 := append([]byte{}, p1Bytes[:point1]...) // 부모1의 첫 구간 + crossed_byt1 = append(crossed_byt1, p2Bytes[point1:point2]...) // 부모2의 중간 구간 + crossed_byt1 = append(crossed_byt1, p1Bytes[point2:]...) // 부모1의 마지막 구간 + + crossed_byt2 := append([]byte{}, p2Bytes[:point1]...) // 부모2의 첫 구간 + crossed_byt2 = append(crossed_byt2, p1Bytes[point1:point2]...) // 부모1의 중간 구간 + crossed_byt2 = append(crossed_byt2, p2Bytes[point2:]...) // 부모2의 마지막 구간 + + result_byt1 := []byte(crossed_byt1) + result_byt2 := []byte(crossed_byt2) + content1[i] = result_byt1 + content2[i] = result_byt2 + case string: + // t1 := tokenizeString(v1) + // v2, ok := parent2.Content[i].(string) + // if !ok { + // panic("type not equal") + // } + // t2 := tokenizeString(v2) + + // c1, c2 := twoPointCrossoverTokens(t1, t2) + // result_str1 := rebuildString(c1) + // result_str2 := rebuildString(c2) + + byt1 := v1 + byt2, ok := parent2.Content[i].(string) + if !ok { + panic("type not equal") + } + p1Bytes := []byte(byt1) + p2Bytes := []byte(byt2) + p1Len := len(p1Bytes) + p2Len := len(p2Bytes) + minLen := p1Len + if p2Len < p1Len { + minLen = p2Len + } + if minLen < 1 { + maxLen := p1Len + m := 1 + if p2Len > p1Len { + m = 2 + maxLen = p2Len + } + if maxLen < 1 { + s := ' ' + bb := byte(s) + content1[i] = string(randomByte_from(bb)) + content2[i] = string(randomByte_from(bb)) + continue + } else { + if m == 1 { + content2[i] = content1[i] + } else { + content1[i] = content2[i] + } + continue + } + } + + point1 := RandRange(0, int64(minLen)) + point2 := RandRange(0, int64(minLen)) + + if point1 > point2 { + point1, point2 = point2, point1 + } + + crossed_byt1 := append([]byte{}, p1Bytes[:point1]...) // 부모1의 첫 구간 + crossed_byt1 = append(crossed_byt1, p2Bytes[point1:point2]...) // 부모2의 중간 구간 + crossed_byt1 = append(crossed_byt1, p1Bytes[point2:]...) // 부모1의 마지막 구간 + + crossed_byt2 := append([]byte{}, p2Bytes[:point1]...) // 부모2의 첫 구간 + crossed_byt2 = append(crossed_byt2, p1Bytes[point1:point2]...) // 부모1의 중간 구간 + crossed_byt2 = append(crossed_byt2, p2Bytes[point2:]...) // 부모2의 마지막 구간 + + result_str1 := string(crossed_byt1) + result_str2 := string(crossed_byt2) + content1[i] = result_str1 + content2[i] = result_str2 + + default: + panic("not supported type") + + } + } + + *seedCount++ + updatedIdv1 := Seed{ + Gen: parent1.Gen + 1, IsCoordinated: false, Content: content1, Result: nil, + Pid: parent1.Id, Id: *seedCount, + } + *seedCount++ + updatedIdv2 := Seed{ + Gen: parent2.Gen + 1, IsCoordinated: false, Content: content2, Result: nil, + Pid: parent1.Id, Id: *seedCount, + } + + return updatedIdv1, updatedIdv2 +} + +// Returns the parents and returns the child. +// I fluidized the number and degree of mating along Gen +// Seeds received as parameters are invariant referenced within the function. +func Evolve(seeds []Seed, seedCount *uint, string_byte_candidates []int) []Seed { + p1 := seeds[0] + Mutation_Range := [10]int{5, 4, 3, 3, 3, 2, 2, 2, 2, 2} + var mutation_range int + if int(p1.Gen) > len(Mutation_Range) { + mutation_range = 1 + } else { + mutation_range = Mutation_Range[p1.Gen-1] + } + Evlove_Count := [10]int{1920, 1440, 1200, 1024, 960, 720, 600, 480, 360, 240} + var evolve_count int + if int(p1.Gen) > len(Evlove_Count) { + evolve_count = 240 + } else { + evolve_count = Evlove_Count[p1.Gen-1] + } + + loop_count := evolve_count / 2 + + new_generation := []Seed{} + + if len(seeds) == 1 { + + for i := 0; i < int(evolve_count); i++ { + new_content := make([]interface{}, len(seeds[0].Content)) + for i, v := range seeds[0].Content { + new_content[i] = v + } + *seedCount++ + new_ind := Seed{ + Gen: seeds[0].Gen + 1, + IsCoordinated: false, + Content: new_content, + Result: nil, + Pid: seeds[0].Id, + Id: *seedCount, + } + + if UniformRandomBool(0.6) { + new_ind = Mutate(new_ind, mutation_range) + } + if UniformRandomBool(0.4) { + new_ind = InsertDelete(new_ind, 0.5, mutation_range, string_byte_candidates) + } + + new_ind.Gen = seeds[0].Gen + 1 + new_generation = append(new_generation, new_ind) + + } + return new_generation + } + + if len(seeds) > 3 { + panic("not covered len") + } + + p2 := seeds[1] + + for i := 0; i < int(loop_count); i++ { + c1, c2 := TwoPointCrossover(p1, p2, seedCount) + new_generation = append(new_generation, c1) + new_generation = append(new_generation, c2) + + } + + for i := range new_generation { + + if UniformRandomBool(0.4) { + new_generation[i] = Mutate(new_generation[i], mutation_range) // 원본 데이터 수정 + } + if UniformRandomBool(0.3) { + new_generation[i] = InsertDelete(new_generation[i], 0.5, mutation_range, string_byte_candidates) + } + + } + + return new_generation +} + +func randomByte_from(seed_byte byte) byte { + p := GetSingleRand().Float64() // 0.0 <= p < 1.0 + + next_flag := GenerateRandomBool(0.5) + var b uint8 + + currentCase := determineCase(rune(seed_byte)) + + if currentCase == 2 { + switch { + case p < 0.45: + currentCase = (currentCase + 3) % 4 + break + case p < 0.55: + break + + case p <= 1.0: + currentCase = (currentCase + 1) % 4 + } + } else { + switch { + case p < 0.1: + currentCase = (currentCase + 2) % 4 + next_flag = false + break + case p >= 0.1 && p < 0.3: + currentCase = (currentCase + 3) % 4 + next_flag = false + break + case p >= 0.3 && p < 0.7: + break + + case p >= 0.7 && p < 0.9: + next_flag = true + currentCase = (currentCase + 1) % 4 + case p >= 0. && p <= 1.0: + next_flag = true + currentCase = (currentCase + 2) % 4 + } + } + + if currentCase == 2 { + p2 := GetSingleRand().Float64() + if next_flag { + if p2 < 0.8 { + currentCase = (currentCase + 1) % 4 + } + } else { + if p2 < 0.8 { + currentCase = (currentCase + 3) % 4 + } + } + } + + switch currentCase { + case 0: + b = uint8(RandRange(ABSOLUTE_MIN, SPECIAL_MAX+1)) + case 1: + b = uint8(RandRange(LOW_ASCII_MIN, LOW_ASCII_MAX+1)) + case 2: + + b = DEL + case 3: + b = uint8(RandRange(HIGH_ASCII_MIN, HIGH_ASCII_MAX+1)) + + } + + return byte(b) +} + +const ( + ABSOLUTE_MIN = 0x00 + SPECIAL_MAX = 0x1F + + LOW_ASCII_MIN = 0x20 + LOW_ASCII_MAX = 0x7E + DEL = 0x7F + + HIGH_ASCII_MIN = 0x80 + HIGH_ASCII_MAX = 0xFF + + PRINT_UNICODE_MIN = 0x100 + PRINT_UNICODE_MAX = 0xD7FF + + BOUNDARY_UNICODE_MIN = 0xD800 + BOUNDARY_UNICODE_MAX = 0x10FFFF + + ABSOLUTE_MAX = 0x7FFFFFFF +) + +func determineCase(seed_rune rune) int { + switch { + case seed_rune >= ABSOLUTE_MIN && seed_rune <= SPECIAL_MAX: + return 0 + case seed_rune >= LOW_ASCII_MIN && seed_rune <= LOW_ASCII_MAX: + return 1 + case seed_rune == DEL: + return 2 + case seed_rune >= HIGH_ASCII_MIN && seed_rune <= HIGH_ASCII_MAX: + return 3 + case seed_rune >= PRINT_UNICODE_MIN && seed_rune <= PRINT_UNICODE_MAX: + return 4 + case seed_rune >= BOUNDARY_UNICODE_MIN && seed_rune <= BOUNDARY_UNICODE_MAX: + return 5 + default: + return 6 + } +} + +func randomInt_from(i interface{}) interface{} { + p := GetSingleRand().Float64() + + switch v := i.(type) { + case int: + var int_std interface{} + if v == 0 { + return int(RandInt64()) + } + switch { + case p < 0.15: + min := int64(v) * (-2) + max := int64(v) * 2 + if min > max { + min, max = max, min + } + int_std = int(RandRange(min, max)) + case p < 0.3: + min := int64(v) * (-4) + max := int64(v) * (4) + if min > max { + min, max = max, min + } + int_std = int(RandRange(min, max)) + case p < 0.45: + min := int64(v) * (-8) + max := int64(v) * (8) + if min > max { + min, max = max, min + } + int_std = int(RandRange(min, max)) + case p < 0.60: + min := int64(v) * (-16) + max := int64(v) * (16) + if min > max { + min, max = max, min + } + int_std = int(RandRange(min, max)) + default: + int_std = GetSingleRand().Int() + } + return int_std + + case int8: + if v == 0 { + return int8(RandInt64()) + } + var int_8 interface{} + switch { + case p < 0.3: + min := int64(v) * (-2) + max := int64(v) * (2) + if min > max { + min, max = max, min + } + int_8 = int8(RandRange(min, max)) + case p < 0.5: + min := int64(v) * (-4) + max := int64(v) * (4) + if min > max { + min, max = max, min + } + int_8 = int8(RandRange(min, max)) + default: + int_8 = int8(RandRange(-128, 128)) + } + return int_8 + + case int16: + if v == 0 { + return int16(RandInt64()) + } + var int_16 interface{} + switch { + case p < 0.3: + min := int64(v) * (-2) + max := int64(v) * (2) + if min > max { + min, max = max, min + } + int_16 = int16(RandRange(min, max)) + case p < 0.5: + min := int64(v) * (-4) + max := int64(v) * (4) + if min > max { + min, max = max, min + } + int_16 = int16(RandRange(min, max)) + default: + int_16 = int16(RandRange(-32768, 32768)) + } + return int_16 + + case int32: + if v == 0 { + return int32(RandInt64()) + } + var int_32 interface{} + switch { + case p < 0.2: + min := int64(v) * (-2) + max := int64(v) * (2) + if min > max { + min, max = max, min + } + int_32 = int32(RandRange(min, max)) + case p < 0.4: + min := int64(v) * (-4) + max := int64(v) * (4) + if min > max { + min, max = max, min + } + int_32 = int32(RandRange(min, max)) + case p < 0.6: + min := int64(v) * (-8) + max := int64(v) * (8) + if min > max { + min, max = max, min + } + int_32 = int32(RandRange(min, max)) + default: + int_32 = GetSingleRand().Int32() + } + return int_32 + + case int64: + if v == 0 { + return RandInt64() + } + var int_64 interface{} + switch { + case p < 0.15: + min := v * (-2) + max := v * (2) + if min > max { + min, max = max, min + } + int_64 = RandRange(min, max) + case p < 0.3: + min := v * (-4) + max := v * (4) + if min > max { + min, max = max, min + } + int_64 = RandRange(min, max) + case p < 0.45: + min := v * (-8) + max := v * (8) + if min > max { + min, max = max, min + } + int_64 = RandRange(min, max) + case p < 0.60: + min := v * (-16) + max := v * (16) + if min > max { + min, max = max, min + } + int_64 = RandRange(min, max) + default: + int_64 = GetSingleRand().Int64() + } + return int_64 + + default: + panic("it's not supported int type") + } +} + +func randomUint_from(u interface{}) interface{} { + p := GetSingleRand().Float64() + + switch v := u.(type) { + case uint: + if v == 0 { + return uint(RandUint64()) + } + var uint_std interface{} + switch { + case p < 0.3: + min := v / 256 + max := v * 2 + uint_std = uint(UintRandRange(uint64(min), uint64(max))) + case p < 0.6: + min := v / 9096 + max := v * 8 + uint_std = uint(UintRandRange(uint64(min), uint64(max))) + default: + uint_std = uint(GetSingleRand().Uint64()) + } + return uint_std + + case uint8: + if v == 0 { + return uint8(RandUint64()) + } + var uint_8 interface{} + switch { + case p < 0.3: + min := int64(v) / 8 + max := int64(v) * 2 + uint_8 = uint8(RandRange(min, max)) + case p < 0.6: + min := int64(v) / 32 + max := int64(v) * 8 + uint_8 = uint8(RandRange(min, max)) + default: + uint_8 = uint8(RandRange(0, 256)) + } + return uint_8 + + case uint16: + if v == 0 { + return uint16(RandUint64()) + } + var uint_16 interface{} + switch { + case p < 0.3: + min := int64(v) / 256 + max := int64(v) * 2 + uint_16 = uint16(RandRange(min, max)) + case p < 0.6: + min := int64(v) / 9096 + max := int64(v) * 8 + uint_16 = uint16(RandRange(min, max)) + default: + uint_16 = uint16(RandRange(0, 65536)) + } + return uint_16 + + case uint32: + if v == 0 { + return uint32(RandUint64()) + } + var uint_32 interface{} + switch { + case p < 0.2: + min := int64(v) / 256 + max := int64(v) * 2 + uint_32 = uint32(RandRange(min, max)) + case p < 0.4: + min := int64(v) / 9096 + max := int64(v) * 8 + uint_32 = uint32(RandRange(min, max)) + case p < 0.6: + min := int64(v) / (9096 * 9096) + max := int64(v) * 16 + uint_32 = uint32(RandRange(min, max)) + default: + uint_32 = uint32(RandRange(0, int64(^uint32(0)))) + } + return uint_32 + + case uint64: + if v == 0 { + return RandUint64() + } + var uint_64 interface{} + switch { + case p < 0.2: + min := v / 256 + max := v * 2 + uint_64 = UintRandRange(min, max) + case p < 0.5: + min := v / 9096 + max := v * 8 + uint_64 = UintRandRange(min, max) + case p < 0.5: + min := v / (9096 * 9096) + max := v * 16 + uint_64 = UintRandRange(min, max) + default: + uint_64 = RandUint64() + } + return uint_64 + + default: + panic("it's not a supported uint type") + } +} + +func randomFloat_from(f interface{}) interface{} { + switch v := f.(type) { + case float32: + var f_32 float32 + f_32 = randFloat32_from(float32(v)) + return float32(f_32) + case float64: + var f_64 float64 + f_64 = randFloat64_from(float64(v)) + return float64(f_64) + default: + panic("argument is not float 32 or float4") + } +} + +func randFloat32_from(f float32) float32 { + return randomFloat32(f) +} + +func randFloat64_from(f float64) float64 { + return randomFloat64(f) +} + +func randomBool() bool { + return UniformRandomBool(0.5) +} + +func factorization_crossover(a interface{}, b interface{}) (interface{}, interface{}) { + switch v1 := a.(type) { + case int: + v2, ok := b.(int) + if !ok { + panic("type not equal") + } + min := v1 + max := v2 + if v1 > v2 { + min = v2 + max = v1 + } + if min < 0 && max < 0 { + min = max + min = min * (-1) + } + if min < 0 { + min = -1 * min + } + if min < 4 { + min = 4 + } + var new_v1 int + var new_v2 int + divisor := int(RandRange(1, int64(min)/2)) + if randomBool() { + new_v1 = v1 / divisor + new_v2 = v2 * divisor + } else { + new_v1 = v1 * divisor + new_v2 = v2 / divisor + } + return new_v1, new_v2 + case int8: + v2, ok := b.(int8) + if !ok { + panic("type not equal") + } + min := v1 + max := v2 + if v1 > v2 { + min = v2 + max = v1 + } + if min < 0 && max < 0 { + min = max + min = min * (-1) + } + if min < 0 { + min = -1 * min + } + if min < 4 { + min = 4 + } + var new_v1 int8 + var new_v2 int8 + divisor := int8(RandRange(1, int64(min)/2)) + if randomBool() { + new_v1 = v1 / divisor + new_v2 = v2 * divisor + } else { + new_v1 = v1 * divisor + new_v2 = v2 / divisor + } + return new_v1, new_v2 + case int16: + v2, ok := b.(int16) + if !ok { + panic("type not equal") + } + min := v1 + max := v2 + if v1 > v2 { + min = v2 + max = v1 + } + if min < 0 && max < 0 { + min = max + min = min * (-1) + } + if min < 0 { + min = -1 * min + } + if min < 4 { + min = 4 + } + var new_v1 int16 + var new_v2 int16 + divisor := int16(RandRange(1, int64(min)/2)) + if randomBool() { + new_v1 = v1 / divisor + new_v2 = v2 * divisor + } else { + new_v1 = v1 * divisor + new_v2 = v2 / divisor + } + + return new_v1, new_v2 + case int32: + v2, ok := b.(int32) + if !ok { + panic("type not equal") + } + min := v1 + max := v2 + if v1 > v2 { + min = v2 + max = v1 + } + if min < 0 && max < 0 { + min = max + min = min * (-1) + } + if min < 0 { + min = -1 * min + } + if min < 4 { + min = 4 + } + var new_v1 int32 + var new_v2 int32 + divisor := int32(RandRange(1, int64(min)/2)) + if randomBool() { + new_v1 = v1 / divisor + new_v2 = v2 * divisor + } else { + new_v1 = v1 * divisor + new_v2 = v2 / divisor + } + return new_v1, new_v2 + + case int64: + v2, ok := b.(int64) + if !ok { + panic("type not equal") + } + min := v1 + max := v2 + if v1 > v2 { + min = v2 + max = v1 + } + if min < 0 && max < 0 { + min = max + min = min * (-1) + } + if min < 0 { + min = -1 * min + } + if min < 4 { + min = 4 + } + var new_v1 int64 + var new_v2 int64 + divisor := RandRange(1, int64(min)/2) + if randomBool() { + new_v1 = v1 / divisor + new_v2 = v2 * divisor + } else { + new_v1 = v1 * divisor + new_v2 = v2 / divisor + } + return new_v1, new_v2 + + case uint: + v2, ok := b.(uint) + if !ok { + panic("type not equal") + } + min := v1 + if v1 > v2 { + min = v2 + } + if min < 4 { + min = 4 + } + var new_v1 uint + var new_v2 uint + divisor := uint(UintRandRange(1, uint64(min)/2)) + if randomBool() { + new_v1 = v1 / divisor + new_v2 = v2 * divisor + } else { + new_v1 = v1 * divisor + new_v2 = v2 / divisor + } + return new_v1, new_v2 + case uint8: + v2, ok := b.(uint8) + if !ok { + panic("type not equal") + } + min := v1 + if v1 > v2 { + min = v2 + } + if min < 4 { + min = 4 + } + var new_v1 uint8 + var new_v2 uint8 + divisor := uint8(UintRandRange(1, uint64(min)/2)) + if randomBool() { + new_v1 = v1 / divisor + new_v2 = v2 * divisor + } else { + new_v1 = v1 * divisor + new_v2 = v2 / divisor + } + return new_v1, new_v2 + + case uint16: + v2, ok := b.(uint16) + if !ok { + panic("type not equal") + } + min := v1 + if v1 > v2 { + min = v2 + } + if min < 4 { + min = 4 + } + var new_v1 uint16 + var new_v2 uint16 + divisor := uint16(UintRandRange(1, uint64(min)/2)) + if randomBool() { + new_v1 = v1 / divisor + new_v2 = v2 * divisor + } else { + new_v1 = v1 * divisor + new_v2 = v2 / divisor + } + return new_v1, new_v2 + + case uint32: + v2, ok := b.(uint32) + if !ok { + panic("type not equal") + } + min := v1 + if v1 > v2 { + min = v2 + } + if min < 4 { + min = 4 + } + var new_v1 uint32 + var new_v2 uint32 + divisor := uint32(UintRandRange(1, uint64(min)/2)) + if randomBool() { + new_v1 = v1 / divisor + new_v2 = v2 * divisor + } else { + new_v1 = v1 * divisor + new_v2 = v2 / divisor + } + return new_v1, new_v2 + + case uint64: + v2, ok := b.(uint64) + if !ok { + panic("type not equal") + } + min := v1 + if v1 > v2 { + min = v2 + } + if min < 4 { + min = 4 + } + var new_v1 uint64 + var new_v2 uint64 + divisor := uint64(UintRandRange(1, uint64(min)/2)) + if randomBool() { + new_v1 = v1 / divisor + new_v2 = v2 * divisor + } else { + new_v1 = v1 * divisor + new_v2 = v2 / divisor + } + return new_v1, new_v2 + + case float32: + v2, ok := b.(float32) + if !ok { + panic("type not equal") + } + new_v1 := float32(0.7*float64(v1) + 0.3*float64(v2)) + new_v2 := float32(0.3*float64(v1) + 0.7*float64(v2)) + return new_v1, new_v2 + case float64: + v2, ok := b.(float64) + if !ok { + panic("type not equal") + } + new_v1 := float64(0.3*float64(v1) + 0.7*float64(v2)) + new_v2 := float64(0.3*float64(v1) + 0.7*float64(v2)) + return new_v1, new_v2 + default: + panic("type can't be factorization_crossovered.") + } +} diff --git a/gnovm/stdlibs/testing/get_coverage.gno b/gnovm/stdlibs/testing/get_coverage.gno new file mode 100644 index 00000000000..b5359a19a98 --- /dev/null +++ b/gnovm/stdlibs/testing/get_coverage.gno @@ -0,0 +1,131 @@ +package testing + +import ( + "errors" + "unicode/utf8" +) + +// TODO: This file is currently a virtual coverage acquisition file +// TODO: To clear one day, replace it with a real coverage function. +// l +// l +// l +func mock(t *T, orig ...interface{}) { + v, ok := orig[0].(string) + if !ok { + panic("dont match") + } + rev := Reverse1(v) + doubleRev := Reverse1(rev) + if v != doubleRev && v == "some cond" { + t.Errorf("Before: %q, after: %q", orig, doubleRev) + } + if utf8.ValidString(v) && !utf8.ValidString(rev) && v == "some cond" { + t.Errorf("Reverse produced invalid UTF-8 string %q", rev) + } +} + +// l +// l +// l +// l +// l +// l +// l +// l +// l +func Reverse1(s string) string { + r := []rune(s) + for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 { + r[i], r[j] = r[j], r[i] + } + return string(r) +} + +func Get_Coverage_of_Reverse1(c *Coverage, s string) string { + r := []rune(s) + *c = append(*c, CoveredLine{co_name: "Reverse1", co_line: 37}) + *c = append(*c, CoveredLine{co_name: "Reverse1", co_line: 38}) + for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 { + *c = append(*c, CoveredLine{co_name: "Reverse1", co_line: 39}) + r[i], r[j] = r[j], r[i] + *c = append(*c, CoveredLine{co_name: "Reverse1", co_line: 40}) + *c = append(*c, CoveredLine{co_name: "Reverse1", co_line: 41}) + } + *c = append(*c, CoveredLine{co_name: "Reverse1", co_line: 39}) + *c = append(*c, CoveredLine{co_name: "Reverse1", co_line: 42}) + return string(r) +} + +func byteToHexChar(b byte) string { + if b < 10 { + return string('0' + b) // 0-9 + } + return string('a' + (b - 10)) // a-f +} + +func Get_Coverage_of_runner(t *T, content []interface{}) Coverage { + // TODO: Make sure get coverage. + // TODO: The format is (function name, line) + + coverage := Coverage{} + coverage = append(coverage, CoveredLine{co_name: "closure", co_line: 13}) + coverage = append(coverage, CoveredLine{co_name: "closure", co_line: 14}) + coverage = append(coverage, CoveredLine{co_name: "closure", co_line: 15}) + v, ok := content[0].(string) + if !ok { + coverage = append(coverage, CoveredLine{co_name: "closure", co_line: 16}) + coverage = append(coverage, CoveredLine{co_name: "closure", co_line: 17}) + + panic("did not implement other type") + } + orig := string(v) + u, ok2 := content[1].(int) + if !ok2 { + panic("did not implement other type") + } + coverage = append(coverage, CoveredLine{co_name: "closure", co_line: 18}) + s1 := Get_Coverage_of_Reverse1(&coverage, orig) + coverage = append(coverage, CoveredLine{co_name: "closure", co_line: 19}) + s2 := Get_Coverage_of_Reverse1(&coverage, s1) + + coverage = append(coverage, CoveredLine{co_name: "closure", co_line: 20}) + if orig != s2 && u > 300 && u < 1000 { + + // println("orig=", orig, "doublereverse", s2) + coverage = append(coverage, CoveredLine{co_name: "closure", co_line: 21}) + return coverage + } + coverage = append(coverage, CoveredLine{co_name: "closure", co_line: 22}) + + coverage = append(coverage, CoveredLine{co_name: "closure", co_line: 23}) + if utf8.ValidString(orig) && !utf8.ValidString(s1) && u > 300 && u < 1000 { + coverage = append(coverage, CoveredLine{co_name: "closure", co_line: 24}) + return coverage + } + coverage = append(coverage, CoveredLine{co_name: "closure", co_line: 25}) + coverage = append(coverage, CoveredLine{co_name: "closure", co_line: 26}) + return coverage +} + +func Get_AllCoverage() Coverage { + return Coverage{ + {"closure", 13}, + {"closure", 14}, + {"Reverse1", 37}, + {"Reverse1", 38}, + {"Reverse1", 39}, + {"Reverse1", 40}, + {"Reverse1", 41}, + {"Reverse1", 42}, + {"closure", 15}, + {"closure", 16}, + {"closure", 17}, + {"closure", 18}, + {"closure", 19}, + {"closure", 20}, + {"closure", 21}, + {"closure", 22}, + {"closure", 23}, + } +} diff --git a/gnovm/stdlibs/testing/hash_machine.gno b/gnovm/stdlibs/testing/hash_machine.gno new file mode 100644 index 00000000000..cb071d7254a --- /dev/null +++ b/gnovm/stdlibs/testing/hash_machine.gno @@ -0,0 +1,150 @@ +package testing + +import ( + "crypto/sha256" + "strings" +) + +type CoveredLine struct { + co_name string + co_line int +} + +type Coverage []CoveredLine + +type ( + internalHash uint64 + HashNumber uint64 +) + +type HashMachine struct { + Internal2Cov_dict map[internalHash]Coverage + HashNumber2Internal *[MAX_CAPACITY]internalHash + internalHash2hashNumber map[internalHash]HashNumber + hashNumber_counter *hashNumberCounter +} + +func (hm *HashMachine) RegisterCoverage2HashNumber(coverage Coverage) HashNumber { + internal_hash := GetInternalHash(coverage) + hm.Internal2Cov_dict[internal_hash] = coverage + hashNumber := hm.Count_HashNumber(internal_hash) + hm.HashNumber2Internal[hashNumber] = internal_hash + return HashNumber(hashNumber) +} + +func (hm *HashMachine) HashNumber2Coverage(hn uint) Coverage { + internal := hm.HashNumber2Internal[hn] + cov := hm.Internal2Cov_dict[internal] + return cov +} + +type hashNumberCounter struct { + counter HashNumber +} + +func New_HashNumberCounter(counter HashNumber) *hashNumberCounter { + return &hashNumberCounter{ + counter: counter, + } +} + +func New_HashMachine() *HashMachine { + return &HashMachine{ + HashNumber2Internal: &[MAX_CAPACITY]internalHash{}, + hashNumber_counter: New_HashNumberCounter(0), + Internal2Cov_dict: make(map[internalHash]Coverage), + internalHash2hashNumber: make(map[internalHash]HashNumber), + } +} + +func (hm *HashMachine) Count_HashNumber(ih internalHash) HashNumber { + if value, exists := hm.internalHash2hashNumber[ih]; exists { + // If the key exists, return the value + return value + } + hm.internalHash2hashNumber[ih] = hm.hashNumber_counter.counter + current := hm.hashNumber_counter.counter + + hm.hashNumber_counter.counter++ + return current +} + +func CoverageToBytes(coverage Coverage) []byte { + var builder strings.Builder + for _, line := range coverage { + builder.WriteString(line.co_name) + builder.WriteString("|") + builder.WriteString(intToString(line.co_line)) + builder.WriteString("|") + } + return []byte(builder.String()) +} + +func intToString(n int) string { + if n == 0 { + return "0" + } + + isNegative := false + if n < 0 { + isNegative = true + n = -n + } + + var digits []byte + for n > 0 { + digit := n % 10 + digits = append([]byte{'0' + byte(digit)}, digits...) + n /= 10 + } + + if isNegative { + digits = append([]byte{'-'}, digits...) + } + + return string(digits) +} + +// calculates hash numbers without removing redundancy of line iterations in coverage +func GetInternalHash(input Coverage) internalHash { + valBytes := CoverageToBytes(input) + valArray := sha256.Sum256(valBytes) + return internalHash(BytesToUint64(valArray)) +} + +// BytesToUint64 converts the first 8 bytes of a SHA256 hash to uint64 +func BytesToUint64(b [32]byte) uint64 { + return uint64(b[0])<<56 | uint64(b[1])<<48 | uint64(b[2])<<40 | uint64(b[3])<<32 | + uint64(b[4])<<24 | uint64(b[5])<<16 | uint64(b[6])<<8 | uint64(b[7]) +} + +func RemoveDuplicates(coverage Coverage) Coverage { + uniqueMap := make(map[CoveredLine]bool) + result := Coverage{} + + for _, line := range coverage { + if !uniqueMap[line] { + uniqueMap[line] = true + result = append(result, line) + } + } + + return result +} + +func FindDifferences(cov1, cov2 Coverage) Coverage { + diff := Coverage{} + for _, line1 := range cov1 { + found := false + for _, line2 := range cov2 { + if line1.co_name == line2.co_name && line1.co_line == line2.co_line { + found = true + break + } + } + if !found { + diff = append(diff, line1) + } + } + return diff +} diff --git a/gnovm/stdlibs/testing/match.gno b/gnovm/stdlibs/testing/match.gno index 3b22602890d..8b099f37624 100644 --- a/gnovm/stdlibs/testing/match.gno +++ b/gnovm/stdlibs/testing/match.gno @@ -16,11 +16,11 @@ import ( type filterMatch interface { // matches checks the name against the receiver's pattern strings using the // given match function. - matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool) + matches(name []string) (ok, partial bool) // verify checks that the receiver's pattern strings are valid filters by // calling the given match function. - verify(name string, matchString func(pat, str string) (bool, error)) error + verify(name string) error } // simpleMatch matches a test name if all of the pattern strings match in @@ -30,43 +30,43 @@ type simpleMatch []string // alternationMatch matches a test name if one of the alternations match. type alternationMatch []filterMatch -func (m simpleMatch) matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool) { +func (m simpleMatch) matches(name []string) (ok, partial bool) { for i, s := range name { if i >= len(m) { break } - if ok, _ := matchString(m[i], s); !ok { + if ok, _ := regexp.MatchString(m[i], s); !ok { return false, false } } return true, len(name) < len(m) } -func (m simpleMatch) verify(name string, matchString func(pat, str string) (bool, error)) error { +func (m simpleMatch) verify(name string) error { for i, s := range m { m[i] = rewrite(s) } // Verify filters before doing any processing. for i, s := range m { - if _, err := matchString(s, "non-empty"); err != nil { + if _, err := regexp.MatchString(s, "non-empty"); err != nil { return fmt.Errorf("element %d of %s (%q): %s", i, name, s, err) } } return nil } -func (m alternationMatch) matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool) { +func (m alternationMatch) matches(name []string) (ok, partial bool) { for _, m := range m { - if ok, partial = m.matches(name, matchString); ok { + if ok, partial = m.matches(name); ok { return ok, partial } } return false, false } -func (m alternationMatch) verify(name string, matchString func(pat, str string) (bool, error)) error { +func (m alternationMatch) verify(name string) error { for i, m := range m { - if err := m.verify(name, matchString); err != nil { + if err := m.verify(name); err != nil { return fmt.Errorf("alternation %d of %s", i, err) } } @@ -164,20 +164,3 @@ func isSpace(r rune) bool { } return false } - -var ( - matchPat string - matchRe *regexp.Regexp -) - -// based on testing/internal/testdeps.TestDeps.MatchString. -func matchString(pat, str string) (result bool, err error) { - if matchRe == nil || matchPat != pat { - matchPat = pat - matchRe, err = regexp.Compile(matchPat) - if err != nil { - return - } - } - return matchRe.MatchString(str), nil -} diff --git a/gnovm/stdlibs/testing/mock/mock_test.gno b/gnovm/stdlibs/testing/mock/mock_test.gno new file mode 100644 index 00000000000..d7fc2e643ab --- /dev/null +++ b/gnovm/stdlibs/testing/mock/mock_test.gno @@ -0,0 +1,34 @@ +package mock + +import ( + "testing" + "unicode/utf8" +) + +func TestMock(t *testing.T) { + println("ss") +} + +func FuzzMock(f *testing.F) { + f.Add("apple hello", int(400002131323)) + f.Add("rainy day", int(98401132231331)) + f.Add("winter comes", int(12349123123)) + f.Fuzz(func(t *testing.T, orig ...interface{}) { + v, ok := orig[0].(string) + if !ok { + panic("dont match") + } + i, ok2 := orig[1].(int) + if !ok2 { + panic("dont match") + } + rev := testing.Reverse1(v) + doubleRev := testing.Reverse1(rev) + if v != doubleRev && i > 300 && i < 500 { + t.Errorf("Before: %q, after: %q", orig, doubleRev) + } + if utf8.ValidString(v) && !utf8.ValidString(rev) && i > 300 && i < 1000 { + t.Errorf("Reverse produced invalid UTF-8 string %q", rev) + } + }) +} diff --git a/gnovm/stdlibs/testing/random.gno b/gnovm/stdlibs/testing/random.gno index 9d55abb6e47..54da4f156a4 100644 --- a/gnovm/stdlibs/testing/random.gno +++ b/gnovm/stdlibs/testing/random.gno @@ -1,10 +1,52 @@ package testing -import "math" +import ( + "math" + "math/rand" +) // Internal state for the random number generator. var x uint64 = 42 +func ExUnixNano() int64 { + return unixNano() +} + +var ( + singleRand *rand.Rand + isCalled bool +) + +type CustomSource struct { + seed uint64 +} + +func (cs *CustomSource) Uint64() uint64 { + cs.seed ^= cs.seed >> 12 + cs.seed ^= cs.seed << 25 + cs.seed ^= cs.seed >> 27 + return cs.seed * 2685821657736338717 +} + +func NewCustomSource(seed int64) *CustomSource { + return &CustomSource{ + seed: uint64(seed), + } +} + +// used a single tone pattern. +// GetGlobalRand: Assume to be a single high-routine environment +func GetSingleRand() *rand.Rand { + if !isCalled { + isCalled = true + seed := unixNano() // 동적 시드 설정 + source := NewCustomSource(seed) + singleRand = rand.New(source) + + } + return singleRand +} + // UniformRand generates a uniformly distributed random number. // It uses the linear congrential generator method to produce the random number. // and the result is in the range from 0 to m-1. here, m is 32768. @@ -20,8 +62,11 @@ func UniformRand() uint64 { // _srand function sets the seed for the random number generator. // This function provides an initial starting point for the sequence of random numbers. -func _srand(seed int64) { - x = uint64(seed) + +// 고쳤습니다. +func _srand() { + r := GetSingleRand() + x = uint64(r.Uint64()) } // nrand function generates a number approximating a normal distribution[1]. @@ -34,31 +79,64 @@ func _srand(seed int64) { // even for K as small as 10, the approximation is quite good. // [1] https://en.wikipedia.org/wiki/Normal_distribution // [2] https://en.wikipedia.org/wiki/Central_limit_theorem -func nrand() float64 { - var i, K uint64 = 0, 10 - var m uint64 = 32768 - var y float64 = 0 - for i = 0; i < K; i++ { - y += float64(UniformRand()) / float64(m) +func nrand() float64 { + r := GetSingleRand() + danger := r.NormFloat64() + scaled := danger / 3 + if scaled < -1 { + return -1 + } else if scaled > 1 { + return 1 } - y = (y - float64(K)/2) / math.Sqrt(float64(K)/12) - return y + return scaled } -// randRange generates a random integer between min and max (inclusive). +// randRange generates a random integer between min and max (inclusive->exclusive: changend). +// ? Random question. Why did you process this intrusive? +// First of all, I changed to Exclusive // This function leverages the UniformRand function to generate a random number in a specified range. // Note: max should be greater than min. -func randRange(min, max int) uint64 { - _min := uint64(min) - _max := uint64(max) - if _min >= _max { - return _min +func RandRange(start, end int64) int64 { + if start >= end { + panic("start >= end ") + } + r := GetSingleRand() + rand_num := r.Int64() + hashed_num := (rand_num) % (int64(end - start)) + result := int64(start) + hashed_num + + return result +} + +func UintRandRange(start, end uint64) uint64 { + if start >= end { + panic("start >= end ") } + r := GetSingleRand() + rand_num := r.Uint64() + hashed_num := (rand_num) % (uint64(end - start)) + result := uint64(start) + hashed_num + + return result +} - rangeSize := _max - _min + 1 - // adjust UniformRand to fit into our range. - return _min + (UniformRand() % rangeSize) +func RandInt64() int64 { + r := GetSingleRand() + rand_num := r.Int64() + return rand_num +} + +func RandUint64() uint64 { + r := GetSingleRand() + rand_num := r.Uint64() + return rand_num +} + +func RandUint32() uint32 { + r := GetSingleRand() + rand_num := r.Uint32() + return rand_num } func GenerateRandomBool(bias float64) bool { @@ -70,3 +148,11 @@ func GenerateRandomBool(bias float64) bool { res := (nrand() + 1) / 2 return res > bias } + +func UniformRandomBool(probability float64) bool { + if probability < 0.0 || probability > 1.0 { + panic("Probability must be between 0.0 and 1.0") + } + r := GetSingleRand() + return r.Float64() < probability +} diff --git a/gnovm/stdlibs/testing/randomFloat64.gno b/gnovm/stdlibs/testing/randomFloat64.gno new file mode 100644 index 00000000000..b97b24925dc --- /dev/null +++ b/gnovm/stdlibs/testing/randomFloat64.gno @@ -0,0 +1,65 @@ +package testing + +import ( + "math" + "time" +) + +func randomFloat32(a float32) float32 { + bits := math.Float32bits(a) + + exponent := (bits >> 23) & 0xFF + mantissa := bits & 0x7FFFFF + sign := bits & 0x80000000 + t := uint32(unixNano()) + manshift := 1 + (t % 7) + + var shift int8 + if exponent <= 1 { + shift = int8(1 + int(mantissa%2)) + } else if exponent >= 0xFE { + shift = int8(-1 - int(mantissa%2)) + } else { + shift = int8(-2 + int(mantissa%5)) + } + + newExp := int32(exponent) + int32(shift) + newExponent := uint32(newExp) + + newMantissa := mantissa ^ (mantissa >> manshift) + + newBits := sign | (newExponent << 23) | (newMantissa & 0x7FFFFF) + + return math.Float32frombits(newBits) +} + +func randomFloat64(a float64) float64 { + bits := math.Float64bits(a) + + exponent := (bits >> 52) & 0x7FF + + mantissa := bits & 0xFFFFFFFFFFFFF + + sign := bits & 0x8000000000000000 + + t := uint64(time.Now().UnixNano()) + manshift := 1 + (t % 7) + + var shift int16 + if exponent <= 1 { + shift = int16(1 + int64(mantissa%2)) + } else if exponent >= 0x7FE { + shift = int16(-1 - int64(mantissa%2)) + } else { + shift = int16(-2 + int64(mantissa%5)) + } + + newExp := int64(exponent) + int64(shift) + newExponent := uint64(newExp) + + newMantissa := mantissa ^ (mantissa >> manshift) + + newBits := sign | (newExponent << 52) | (newMantissa & 0xFFFFFFFFFFFFF) + + return math.Float64frombits(newBits) +} diff --git a/gnovm/stdlibs/testing/random_test.gno b/gnovm/stdlibs/testing/random_test.gno index 7fcc00552ea..9363d67df86 100644 --- a/gnovm/stdlibs/testing/random_test.gno +++ b/gnovm/stdlibs/testing/random_test.gno @@ -6,8 +6,8 @@ import ( ) func updateSeed() { - seed := time.Now().UnixNano() - _srand(seed) + seed := unixNano() + _srand() } func Test_UniformRand(t *T) { @@ -66,7 +66,7 @@ func Test_GenerateRandomBool(t *T) { falseCount++ } } - + //? This is also a question. Why do you write a test case like this? if trueCount == 0 || falseCount == 0 { t.Errorf("Bias = %v, trueCount = %v, falseCount = %v, want both > 0", bias, trueCount, falseCount) } @@ -80,19 +80,25 @@ func Test_GenerateRandomBool(t *T) { func TestRandRange(t *T) { nums := make(map[uint64]int) for i := 0; i < 1000; i++ { - res := randRange(0, 10) + res := RandRange(0, 10) if res < 0 || res > 10 { t.Errorf("gerandRangenerateRange() = %v, want in range [0, 9]", res) } + res2 := uint64(res) - if _, ok := nums[res]; ok { - nums[res]++ + if _, ok := nums[res2]; ok { + nums[res2]++ } else { - nums[res] = 1 + nums[res2] = 1 } } - if len(nums) != 11 { + // if len(nums) != 11 { + //? I modified it because the teke was weird. I think it should be 10. + // for num, count := range nums { + // t.Errorf("Number %d: %d times\n", num, count) + // } + if len(nums) != 10 { t.Errorf("len(nums) = %v, want in range [0, 10]", len(nums)) } } diff --git a/gnovm/stdlibs/testing/seed_queue.gno b/gnovm/stdlibs/testing/seed_queue.gno new file mode 100644 index 00000000000..dc2e86c27fc --- /dev/null +++ b/gnovm/stdlibs/testing/seed_queue.gno @@ -0,0 +1,62 @@ +package testing + +import "errors" + +type Seed_Queue struct { + data []Seed +} + +func New_Seed_Queue() *Seed_Queue { + return &Seed_Queue{ + data: make([]Seed, 0), + } +} + +func (q *Seed_Queue) Enqueue(seed Seed) { + q.data = append(q.data, seed) +} + +func (q *Seed_Queue) Dequeue() (Seed, bool) { + if len(q.data) == 0 { + return Seed{}, false + } + + front := q.data[0] + + q.data = q.data[1:] + // Reduce memory usage + if len(q.data) > 0 && len(q.data) <= cap(q.data)/2 { + newData := make([]Seed, len(q.data)) + copy(newData, q.data) + q.data = newData + } + return front, true +} + +func (q *Seed_Queue) Peek() (Seed, error) { + if len(q.data) == 0 { + return Seed{}, errors.New("queue is empty") + } + return q.data[0], nil +} + +func (q *Seed_Queue) Select() Seed + +func (q *Seed_Queue) IsEmpty() bool { + return len(q.data) == 0 +} + +func (q *Seed_Queue) Size() int { + return len(q.data) +} + +func (q *Seed_Queue) Display() { + if len(q.data) == 0 { + println("Queue is empty") + return + } + println("Queue seeds:") + for i, seed := range q.data { + println("[", i, "]: {content:", seed.Content, "}") + } +} diff --git a/gnovm/stdlibs/testing/state_machine.gno b/gnovm/stdlibs/testing/state_machine.gno new file mode 100644 index 00000000000..d9bbdc994c3 --- /dev/null +++ b/gnovm/stdlibs/testing/state_machine.gno @@ -0,0 +1,351 @@ +package testing + +import ( + "sort" + "strconv" + "strings" +) + +type SupportedType string + +const ( + Byte_Array SupportedType = "[]byte" + String SupportedType = "string" + Bool SupportedType = "bool" + Byte SupportedType = "byte" + Rune SupportedType = "rune" + Float32 SupportedType = "float32" + Float64 SupportedType = "float64" + Int SupportedType = "int" + Int8 SupportedType = "int8" + Int16 SupportedType = "int16" + Int32 SupportedType = "int32" + Int64 SupportedType = "int64" + Uint SupportedType = "uint" + Uint8 SupportedType = "uint8" + Uint16 SupportedType = "uint16" + Uint32 SupportedType = "uint32" + Uint64 SupportedType = "uint64" +) + +type Seed struct { + Pid uint + Id uint + Gen uint + IsCoordinated bool + HashNumber HashNumber + Content []interface{} + Result interface{} +} + +const ( + // It covers 100,000 types of paths (including the differences in paths according to the repeat statements as individual terminations). + // Effective when pathHash is less than 100,000. + // More than that is possible, but less effective. + MAX_CAPACITY uint64 = 100_000 + Partial_Capacity = MAX_CAPACITY / 5 +) + +type StateMachine struct { + Trials uint + Input_count uint + + All_Coverage Coverage // []coveredline + Covered_Coverage Coverage // []coveredline + + SeedType []SupportedType + init_hashnumber []HashNumber + string_byte_candidates []int + + Priority_Cache uint + HashNumber2Seeds *[MAX_CAPACITY]*Seed_Queue + HashNumber2Priority *[MAX_CAPACITY]uint + Priority2HashNumberULL *[MAX_CAPACITY]*Unique_Uint_LinkedList + + Crash_Logger *Crash_Logger + verbose bool + inspectingHashNumbers uint +} + +func New_StateMachine(trials uint, inputCount uint) *StateMachine { + setted_AllULL := func() *[MAX_CAPACITY]*Unique_Uint_LinkedList { + arr := &[MAX_CAPACITY]*Unique_Uint_LinkedList{} + for i := range arr { + arr[i] = New_Unique_Uint_LinkedList() + } + return arr + }() + return &StateMachine{ + Input_count: inputCount, + Trials: trials, + All_Coverage: Coverage{{co_name: "nil", co_line: 0}}, + Covered_Coverage: Coverage{{co_name: "nil", co_line: 0}}, + + Priority_Cache: 1, + + HashNumber2Priority: &[MAX_CAPACITY]uint{}, + HashNumber2Seeds: &[MAX_CAPACITY]*Seed_Queue{}, + Priority2HashNumberULL: setted_AllULL, + + inspectingHashNumbers: 0, + Crash_Logger: New_Crash_Logger(), + } +} + +type EndInfo struct { + Complete_Trials bool + MAXed_CAPACITY bool +} + +func (sm *StateMachine) CoordinateAbstraction(hn HashNumber, abstract_number uint) EndInfo { + sm.Input_count = sm.Input_count + abstract_number + sm.HashNumber2Priority[hn] = sm.HashNumber2Priority[hn] + abstract_number + + if sm.Input_count >= sm.Trials { + sm.Input_count = sm.Trials + return EndInfo{ + Complete_Trials: true, + MAXed_CAPACITY: false, + } + + } else if uint64(sm.Input_count)%MAX_CAPACITY == 0 { + return EndInfo{ + Complete_Trials: false, + MAXed_CAPACITY: true, + } + } else { + return EndInfo{ + Complete_Trials: false, + MAXed_CAPACITY: false, + } + } +} + +func (sm *StateMachine) CoordinateSeed(seed Seed) EndInfo { + if seed.IsCoordinated { + sm.HashNumber2Seeds[seed.HashNumber].Enqueue(seed) + return EndInfo{ + Complete_Trials: false, + MAXed_CAPACITY: false, + } + } + hn := seed.HashNumber + seed.IsCoordinated = true + sm.Input_count++ + + if sm.HashNumber2Seeds[hn] == nil { + sm.inspectingHashNumbers++ + sm.HashNumber2Seeds[hn] = New_Seed_Queue() + } + sm.HashNumber2Seeds[hn].Enqueue(seed) + old_priority := sm.HashNumber2Priority[hn] + sm.HashNumber2Priority[hn]++ + + updated_priority := sm.HashNumber2Priority[hn] + + if updated_priority == 1 { + + sm.Priority_Cache = 1 + sm.Priority2HashNumberULL[updated_priority].Append(uint(hn)) + } else { + sm.Priority2HashNumberULL[old_priority].Delete(uint(hn)) + sm.Priority2HashNumberULL[updated_priority].Append(uint(hn)) + } + if sm.verbose { + if sm.Input_count%1000 == 0 { + println(sm.Input_count, "times runned: inspecting", sm.inspectingHashNumbers, "coverages") + } + } else { + if sm.Input_count%(sm.Trials/5) == 0 { + println(sm.Input_count, "times runned: inspecting", sm.inspectingHashNumbers, "coverages") + } + } + + if sm.Input_count >= sm.Trials { + return EndInfo{ + Complete_Trials: true, + MAXed_CAPACITY: false, + } + } else if uint64(sm.Input_count)%MAX_CAPACITY == 0 { + return EndInfo{ + Complete_Trials: false, + MAXed_CAPACITY: true, + } + } else { + return EndInfo{ + Complete_Trials: false, + MAXed_CAPACITY: false, + } + } +} + +func (sm *StateMachine) PopInitSeedByHN(hn HashNumber) Seed { + popedSeed, isOnce := sm.HashNumber2Seeds[hn].Dequeue() + if !isOnce { + panic("logical internal error: it must has more than one seed") + } + return popedSeed +} + +func (sm *StateMachine) PopSeeds() []Seed { + var highest_hn uint + + for { + + hn, isExist := sm.Priority2HashNumberULL[sm.Priority_Cache].Peek() + if !isExist { + sm.Priority_Cache++ + + continue + } + + highest_hn = hn + break + } + + popedSeed_1, isOnce := sm.HashNumber2Seeds[highest_hn].Dequeue() + if !isOnce { + panic("logical internal error: it must has more than one seed") + } + peekedSeed_2, err := sm.HashNumber2Seeds[highest_hn].Peek() + if err != nil { + return []Seed{popedSeed_1} + } + pid_1 := popedSeed_1.Pid + pid_2 := peekedSeed_2.Pid + if pid_1 == pid_2 { + popedSeed_2, _ := sm.HashNumber2Seeds[highest_hn].Dequeue() + return []Seed{popedSeed_1, popedSeed_2} + } else { + return []Seed{popedSeed_1} + } +} + +type P_HN_Pair struct { + Priority uint + HashNumber int +} +type PHP_List []P_HN_Pair + +func (phpl PHP_List) Len() int { return len(phpl) } +func (phpl PHP_List) Less(i, j int) bool { return phpl[i].Priority < phpl[j].Priority } +func (phpl PHP_List) Swap(i, j int) { phpl[i], phpl[j] = phpl[j], phpl[i] } + +type S_P_Pair struct { + Seed Seed + Priority uint +} + +func (sm *StateMachine) Summarize() []Seed { + collected_P_HN_Pair := PHP_List{} + for i, e := range sm.HashNumber2Priority { + + if e == 0 { + break + } + collected_P_HN_Pair = append(collected_P_HN_Pair, P_HN_Pair{ + Priority: e, + HashNumber: i, + }) + // 슬라이스 길이가 Partial_Capacity를를 초과하면 종료 + if len(collected_P_HN_Pair) >= int(Partial_Capacity) { + break + } + } + + sort.Sort(collected_P_HN_Pair) + + sampled_sp_pair := []S_P_Pair{} + + for _, pair := range collected_P_HN_Pair { + hn := pair.HashNumber + seed := sm.HashNumber2Seeds[hn].data[0] + priority := pair.Priority + sampled_sp_pair = append(sampled_sp_pair, S_P_Pair{seed, priority}) + } + + total := len(sampled_sp_pair) + + summarized_seeds := []Seed{} + + for i, sp_pair := range sampled_sp_pair { + + rank := i + 1 + + seed := sp_pair.Seed + seed.Pid = 0 + seed.Id = uint(i) + seed.Gen = genAllocation(sp_pair.Priority, rank, total) + seed.IsCoordinated = false + + summarized_seeds = append(summarized_seeds, seed) + } + return summarized_seeds +} + +func genAllocation(priority uint, rank int, total int) uint { + var bigger uint + + switch rank { + case 1: + bigger = 1 + case 2: + bigger = 2 + case 3: + bigger = 3 + case 4: + bigger = 4 + case 5: + bigger = 5 + default: + bigger = 15 + } + c1_cutLine := uint(float64(MAX_CAPACITY) * 0.008) + c2_cutLine := uint(float64(MAX_CAPACITY) * 0.014) + c3_cutLine := uint(float64(MAX_CAPACITY) * 0.02) + c4_cutLine := uint(float64(MAX_CAPACITY) * 0.03) + c5_cutLine := uint(float64(MAX_CAPACITY) * 0.08) + c6_cutLine := uint(float64(MAX_CAPACITY) * 0.1) + c7_cutLine := uint(float64(MAX_CAPACITY) * 0.15) + c8_cutLine := uint(float64(MAX_CAPACITY) * 0.30) + switch { + case priority <= c1_cutLine: + if bigger > 1 { + bigger = 1 + } + case priority <= c2_cutLine: + if bigger > 2 { + bigger = 2 + } + case priority <= c3_cutLine: + if bigger > 3 { + bigger = 3 + } + case priority <= c4_cutLine: + if bigger > 4 { + bigger = 4 + } + case priority <= c5_cutLine: + if bigger > 5 { + bigger = 5 + } + case priority <= c6_cutLine: + if bigger > 6 { + bigger = 6 + } + case priority <= c7_cutLine: + if bigger > 7 { + bigger = 7 + } + case priority <= c8_cutLine: + if bigger > 10 { + bigger = 10 + } + default: + + if bigger > 15 { + bigger = 15 + } + } + return bigger +} diff --git a/gnovm/stdlibs/testing/testing.gno b/gnovm/stdlibs/testing/testing.gno index 6e55c5cc283..46d994fe62e 100644 --- a/gnovm/stdlibs/testing/testing.gno +++ b/gnovm/stdlibs/testing/testing.gno @@ -75,11 +75,29 @@ type T struct { dur string } +type FuzzingInfo struct { + Failed bool + Output []byte + Dur string +} + +func (t *T) GetResult() FuzzingInfo { + info := FuzzingInfo{ + Failed: t.failed, + Output: t.output, + Dur: t.dur, + } + return info +} + func NewT(name string) *T { return &T{name: name} } -type testingFunc func(*T) +type ( + testingFunc func(*T) + fuzzingFunc func(*F) +) // Not yet implemented: // func (t *T) Cleanup(f func()) { @@ -264,7 +282,6 @@ func (b *B) TempDir() string { panic("not yet implemen // ---------------------------------------- // PB // TODO: actually implement - type PB struct{} func (pb *PB) Next() bool { panic("not yet implemented") } @@ -273,6 +290,10 @@ type InternalTest struct { Name string F testingFunc } +type InternalFuzz struct { + Name string + F fuzzingFunc +} func (t *T) shouldRun(name string) bool { if t.runFilter == nil { @@ -280,12 +301,13 @@ func (t *T) shouldRun(name string) bool { } elem := strings.Split(name, "/") - ok, partial := t.runFilter.matches(elem, matchString) + ok, partial := t.runFilter.matches(elem) _ = partial // we don't care right now return ok } func RunTest(runFlag string, verbose bool, test InternalTest) (ret string) { + // func RunTest(runFlag string, verbose bool, test InternalTest) (ret string) { t := &T{ name: test.Name, verbose: verbose, @@ -307,6 +329,35 @@ func RunTest(runFlag string, verbose bool, test InternalTest) (ret string) { return string(out) } +func RunFuzz(fuzzName string, fuzzIters uint, verbose bool, fuzz InternalFuzz) (ret string) { + var report Report + fuzzer := New_F(verbose, fuzzIters) + if fuzzName == "" { + out, _ := json.Marshal(Report{ + Failed: false, + Skipped: true, + }) + return string(out) + } + if strings.HasPrefix(fuzz.Name, fuzzName) { + + fmt.Fprintln(os.Stderr, "fuzz", fuzz.Name, ":", strconv.Itoa(int(fuzzIters)), "iterate") + fRunner(fuzzer, fuzz.F) + report = Report{ + Failed: fuzzer.failed, + Skipped: true, + } + } else { + report = Report{ + Failed: false, + Skipped: true, + } + } + + out, _ := json.Marshal(report) + return string(out) +} + func formatDur(dur int64) string { // XXX switch to FormatFloat after it's been added // 1 sec = 1e9 nsec @@ -322,6 +373,17 @@ func formatDur(dur int64) string { // used to calculate execution times; only present in testing stdlibs func unixNano() int64 +func fRunner(f *F, fn fuzzingFunc) { + start := unixNano() + defer func() { + recover() + dur := unixNano() - start + f.dur = formatDur(dur) + fmt.Fprintln(os.Stderr, "Elapsed", f.dur) + }() + fn(f) +} + func tRunner(t *T, fn testingFunc, verbose bool) { if !t.shouldRun(t.name) { return diff --git a/gnovm/stdlibs/testing/to_string.gno b/gnovm/stdlibs/testing/to_string.gno new file mode 100644 index 00000000000..43283ecb960 --- /dev/null +++ b/gnovm/stdlibs/testing/to_string.gno @@ -0,0 +1,253 @@ +package testing + +import ( + "bytes" + "strconv" + "strings" + "time" + "unicode/utf8" +) + +// Token distinguishes whether it is "normal Unicode" or "broken bytes". +type Token struct { + Data []byte // Actual bytes of the token + Valid bool // If true, characters that have successfully decoded UTF-8 +} + +// tokenizeString: Decode string s as UTF-8 as much as possible, keep broken bytes separate +func TokenizeString(s string) []Token { + var tokens []Token + b := []byte(s) + i := 0 + for i < len(b) { + r, size := utf8.DecodeRune(b[i:]) + switch { + case r == utf8.RuneError && size == 1: + tokens = append(tokens, Token{ + Data: []byte{b[i]}, + Valid: false, + }) + i++ + default: + tokens = append(tokens, Token{ + Data: b[i : i+size], + Valid: true, + }) + i += size + } + } + return tokens +} + +func RebuildString(tokens []Token) string { + var buf bytes.Buffer + for _, t := range tokens { + buf.Write(t.Data) + } + return buf.String() +} + +func RebuildEscaped(tokens []Token) string { + var result []byte + for _, t := range tokens { + if t.Valid { + result = append(result, t.Data...) + } else { + for _, b := range t.Data { + result = append(result, []byte("\\x")...) + hex := byteToHex(b) + result = append(result, hex...) + } + } + } + return string(result) +} + +func byteToHex(b byte) []byte { + const hexdigits = "0123456789abcdef" + hi := hexdigits[b>>4] + lo := hexdigits[b&0x0F] + return []byte{hi, lo} +} + +func TransForHuman(s string) string { + toks := TokenizeString(s) + escaped := RebuildEscaped(toks) + return escaped +} + +func uintToString(v uint) string { + return strconv.Itoa(int(v)) +} + +func boolToString(b bool) string { + if b { + return "true" + } + return "false" +} + +func sliceToString(slice []uint) string { + if len(slice) == 0 { + return "[]" + } + + var sb strings.Builder + sb.WriteString("[") + for i, val := range slice { + sb.WriteString(uintToString(val)) + if i < len(slice)-1 { + sb.WriteString(", ") + } + } + sb.WriteString("]") + return sb.String() +} + +func interfaceToString(i interface{}) string { + switch v := i.(type) { + case nil: + return "nil" + case string: + return strconv.Quote(v) + case int, int32, int64: + return strconv.FormatInt(int64(v.(int)), 10) + case uint, uint32, uint64: + return strconv.FormatUint(uint64(v.(uint)), 10) + case float32, float64: + return strconv.FormatFloat(v.(float64), 'f', -1, 64) + case bool: + if v { + return "true" + } + return "false" + default: + return "unknown" + } +} + +func interfacesliceToString(slice []interface{}) string { + var sb strings.Builder + sb.WriteString("[") + for i, elem := range slice { + sb.WriteString(interfaceToString(elem)) + if i < len(slice)-1 { + sb.WriteString(", ") + } + } + sb.WriteString("]") + return sb.String() +} + +func CoverageToString(coverage Coverage) string { + var sb strings.Builder + sb.WriteString("[") + count := 0 + for i, line := range coverage { + sb.WriteString("{co_name: ") + sb.WriteString(line.co_name) + sb.WriteString(", co_line: ") + sb.WriteString(strconv.Itoa(line.co_line)) + sb.WriteString("}") + count++ + if i < len(coverage)-1 { + if count%3 == 0 { + sb.WriteString(", \n") + } else { + sb.WriteString(", ") + } + } + } + sb.WriteString("]") + return sb.String() +} + +func ContentToString(content []interface{}) string { + var result strings.Builder + + result.WriteString("[") + for i, elem := range content { + switch v := elem.(type) { + case string: + result.WriteString("\"" + v + "\"") + case int: + + result.WriteString(strconv.Itoa(v)) + case int8: + + result.WriteString(strconv.FormatInt(int64(v), 10)) + case int16: + + result.WriteString(strconv.FormatInt(int64(v), 10)) + case int32: + + result.WriteString(strconv.FormatInt(int64(v), 10)) + case int64: + + result.WriteString(strconv.FormatInt(v, 10)) + case uint: + + result.WriteString(strconv.FormatUint(uint64(v), 10)) + case uint8: + + result.WriteString(strconv.FormatUint(uint64(v), 10)) + case uint16: + + result.WriteString(strconv.FormatUint(uint64(v), 10)) + case uint32: + + result.WriteString(strconv.FormatUint(uint64(v), 10)) + case uint64: + + result.WriteString(strconv.FormatUint(v, 10)) + case float32: + + result.WriteString(strconv.FormatFloat(float64(v), 'f', -1, 32)) + case float64: + + result.WriteString(strconv.FormatFloat(v, 'f', -1, 64)) + case []byte: + + result.WriteString("\"" + string(v) + "\"") + case bool: + + if v { + result.WriteString("true") + } else { + result.WriteString("false") + } + default: + + result.WriteString("unknown") + } + + if i < len(content)-1 { + result.WriteString(", ") + } + } + result.WriteString("]") + + return result.String() +} + +func CrashCaseToString(c Crash_Case) string { + var sb strings.Builder + + sb.WriteString("{ \n") + + sb.WriteString("Input: ") + sb.WriteString(TransForHuman(c.Input)) + if c.IsPanic { + sb.WriteString("\nPanic/Error: Panic ") + sb.WriteString("\nPanicMessage: \"") + sb.WriteString(c.PanicMessage) + } else { + sb.WriteString("\nPanic/Error: Error ") + sb.WriteString("\nErrorMessage: ") + sb.WriteString("\"" + c.ErrorMsg + "\"") + } + + sb.WriteString("\n}") + + return sb.String() +} diff --git a/gnovm/stdlibs/testing/uint_queue.gno b/gnovm/stdlibs/testing/uint_queue.gno new file mode 100644 index 00000000000..62838d83bfe --- /dev/null +++ b/gnovm/stdlibs/testing/uint_queue.gno @@ -0,0 +1,53 @@ +package testing + +type Uint_Queue struct { + data []uint +} + +func New_Uint_Queue() *Uint_Queue { + return &Uint_Queue{data: make([]uint, 0)} +} + +func (q *Uint_Queue) Uint_Enqueue(value uint) { + q.data = append(q.data, value) +} + +// Remove from queue and return. +// If the slice is empty compared to the capacity, +// Added logic to copy into new slices to organize memory. +func (q *Uint_Queue) Uint_Dequeue() (uint, bool) { + if len(q.data) == 0 { + return 0, false + } + + value := q.data[0] + q.data[0] = 0 + + q.data = q.data[1:] + + // Memory leak prevention + // When the current slice length (len) is less than half the capacity (cap) + // Copy to new slice, remove the front discarded space. + if len(q.data) > 0 && len(q.data) <= cap(q.data)/2 { + newData := make([]uint, len(q.data)) + copy(newData, q.data) + q.data = newData + } + + return value, true +} + +func (q *Uint_Queue) Uint_Peek() (uint, bool) { + if len(q.data) == 0 { + return 0, false + } + return q.data[0], true +} + +func (q *Uint_Queue) IsEmpty() bool { + return len(q.data) == 0 +} + +func (q *Uint_Queue) Size() int { + return len(q.data) +} diff --git a/gnovm/stdlibs/testing/unique_uint_linked_list.gno b/gnovm/stdlibs/testing/unique_uint_linked_list.gno new file mode 100644 index 00000000000..0eb1a6bb1de --- /dev/null +++ b/gnovm/stdlibs/testing/unique_uint_linked_list.gno @@ -0,0 +1,168 @@ +package testing + +// ! Data must be unique!!! +// ! It's a list that reduces time complexity by using it!! +// Node: Node in single connection list +type Node struct { + data uint + next *Node +} + +// Unique_Uint_LinkedList: Data Structure for Singly Linked List + O(1) Insertion/Deletion +type Unique_Uint_LinkedList struct { + head *Node + tail *Node + nodeMap map[uint]*Node // data -> node + parentMap map[*Node]*Node // node -> parent node +} + +func New_Unique_Uint_LinkedList() *Unique_Uint_LinkedList { + return &Unique_Uint_LinkedList{ + head: nil, + tail: nil, + nodeMap: make(map[uint]*Node), + parentMap: make(map[*Node]*Node), + } +} + +// ------------------------------------------------------- +// 1) Add a new node to O(1) at the end of the Appendix list +// ------------------------------------------------------- +func (ll *Unique_Uint_LinkedList) Append(data uint) { + newNode := &Node{data: data, next: nil} + + // 빈 리스트인 경우 + if ll.head == nil { + ll.head = newNode + ll.tail = newNode + ll.nodeMap[data] = newNode + ll.parentMap[newNode] = nil + return + } + + ll.tail.next = newNode + ll.parentMap[newNode] = ll.tail + ll.tail = newNode + ll.nodeMap[data] = newNode +} + +// ------------------------------------------------------- +// 2) Prepend: Add a new node to O(1) in the head of the list +// ------------------------------------------------------- +func (ll *Unique_Uint_LinkedList) Prepend(data uint) { + newNode := &Node{data: data} + + if ll.head == nil { + ll.head = newNode + ll.tail = newNode + ll.nodeMap[data] = newNode + ll.parentMap[newNode] = nil + return + } + + newNode.next = ll.head + + ll.parentMap[ll.head] = newNode + + ll.head = newNode + + ll.nodeMap[data] = newNode + ll.parentMap[newNode] = nil +} + +// ------------------------------------------------------- +// 3) Delete: Delete node with data == value to O(1) +// ------------------------------------------------------- +func (ll *Unique_Uint_LinkedList) Delete(value uint) { + targetNode, ok := ll.nodeMap[value] + if !ok { + return + } + + if targetNode == ll.head { + ll.head = ll.head.next + if ll.head == nil { + ll.tail = nil + } else { + ll.parentMap[ll.head] = nil + } + delete(ll.nodeMap, value) + delete(ll.parentMap, targetNode) + return + } + + parent := ll.parentMap[targetNode] + if parent == nil { + return + } + parent.next = targetNode.next + + if targetNode == ll.tail { + ll.tail = parent + } else { + ll.parentMap[targetNode.next] = parent + } + + delete(ll.nodeMap, value) + delete(ll.parentMap, targetNode) +} + +// ------------------------------------------------------- +// 4) DeleteNode: Delete directly to O(1) with node pointer +// ------------------------------------------------------- +func (ll *Unique_Uint_LinkedList) DeleteNode(node *Node) { + if node == nil { + return + } + + if node == ll.head { + ll.head = ll.head.next + if ll.head == nil { + ll.tail = nil + } else { + ll.parentMap[ll.head] = nil + } + delete(ll.nodeMap, node.data) + delete(ll.parentMap, node) + return + } + + parent := ll.parentMap[node] + if parent == nil { + return + } + parent.next = node.next + + if node == ll.tail { + ll.tail = parent + } else { + ll.parentMap[node.next] = parent + } + + delete(ll.nodeMap, node.data) + delete(ll.parentMap, node) +} + +func (ll *Unique_Uint_LinkedList) SearchNode(value uint) *Node { + return ll.nodeMap[value] +} + +func (ll *Unique_Uint_LinkedList) Display() { + current := ll.head + for current != nil { + println("%d -> ", current.data) + current = current.next + } + println("nil") +} + +func (ll *Unique_Uint_LinkedList) IsEmpty() bool { + return ll.head == nil +} + +func (ll *Unique_Uint_LinkedList) Peek() (uint, bool) { + if ll.head == nil { + return 0, false + } + return ll.head.data, true +} diff --git a/go.mod b/go.mod index f73ba1926e6..3ac3385d089 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/gnolang/gno go 1.22 -toolchain go1.22.4 +toolchain go1.22.0 require ( dario.cat/mergo v1.0.1 diff --git a/misc/genstd/mapping.go b/misc/genstd/mapping.go index 8b70e2f512d..1a86fca52bc 100644 --- a/misc/genstd/mapping.go +++ b/misc/genstd/mapping.go @@ -231,9 +231,22 @@ func (m *mapping) typesEqual(gnoe, goe ast.Expr) error { } return nil + // 기존에 "panic("not implemented")"만 있던 부분을 세분화. + case *ast.InterfaceType: + goItf, ok := goe.(*ast.InterfaceType) + if !ok { + return &mismatch + } + // 둘 다 “빈 인터페이스(interface{})”인 경우에만 매칭 OK로 본다. + if isEmptyInterface(gnoe) && isEmptyInterface(goItf) { + return nil + } + // 그 외엔 아직 처리 안 함 → mismatch + panic("not implemented") + case *ast.StructType, *ast.FuncType, - *ast.InterfaceType, + *ast.MapType, *ast.Ellipsis, *ast.SelectorExpr: @@ -244,6 +257,11 @@ func (m *mapping) typesEqual(gnoe, goe ast.Expr) error { } } +func isEmptyInterface(itf *ast.InterfaceType) bool { + // interface{} → 메서드가 전혀 없는 인터페이스 + return itf.Methods == nil || len(itf.Methods.List) == 0 +} + // returns full import path from package ident func resolveImport(imports []*ast.ImportSpec, ident string) string { for _, i := range imports {