From 3281bbdec10445adf8996c1948b2d16c0df3c0ee Mon Sep 17 00:00:00 2001 From: xhd2015 Date: Sat, 27 Jul 2024 15:18:01 +0800 Subject: [PATCH] make build cache aware of trace flags --- .github/workflows/go-compatible.yml | 2 +- .github/workflows/go-next.yml | 3 +- .github/workflows/go-windows.yml | 2 +- .github/workflows/go.yml | 2 +- cmd/xgo/exec_tool/debug.go | 5 + cmd/xgo/exec_tool/env.go | 6 + cmd/xgo/main.go | 26 ++++- cmd/xgo/option.go | 83 +++++++++++--- cmd/xgo/runtime_gen/trace/trace.go | 50 +++++---- cmd/xgo/runtime_gen/trap/flags/build_cache.go | 5 + cmd/xgo/runtime_gen/trap/flags/flags.go | 31 ++++++ cmd/xgo/version.go | 4 +- patch/ctxt/ctx.go | 1 + patch/syntax/syntax.go | 34 ++++++ .../persistent_after_build/persistent.go | 1 + .../persistent_after_build/persistent_test.go | 40 +++++++ .../testdata/flags_test.go | 23 ++++ .../persistent_after_build/testdata/go.mod | 7 ++ runtime/trace/trace.go | 50 +++++---- runtime/trap/flags/build_cache.go | 5 + runtime/trap/flags/flags.go | 31 ++++++ script/run-test/main.go | 20 +++- support/assert/err.go | 4 +- support/transform/edit/line/line.go | 39 ++++++- support/transform/patch/format/format.go | 17 +++ support/transform/patch/parse.go | 34 +++--- support/transform/patch/patch.go | 32 ++++-- support/transform/patch/patch_test.go | 27 +++-- .../patch/testdata/const_decl/expected.go | 9 +- .../patch/testdata/hello_world/expected.go | 2 + .../patch/testdata/import/expected.go | 6 +- .../patch/testdata/prepend_func/expected.go | 3 + .../patch/testdata/replace/expected.go | 9 +- .../unpatch/testdata/duplicate/expect.go | 7 ++ .../unpatch/testdata/duplicate/original.go | 17 +++ .../testdata/missing_close/original.go | 12 ++ .../unpatch/testdata/missing_end/original.go | 12 ++ .../unpatch/testdata/missing_id/original.go | 12 ++ .../testdata/recover_replaced/expect.go | 9 ++ .../testdata/recover_replaced/original.go | 19 ++++ .../patch/unpatch/testdata/simple/expect.go | 7 ++ .../patch/unpatch/testdata/simple/original.go | 12 ++ support/transform/patch/unpatch/unpatch.go | 103 ++++++++++++++++++ .../transform/patch/unpatch/unpatch_test.go | 95 ++++++++++++++++ 44 files changed, 805 insertions(+), 113 deletions(-) create mode 100755 cmd/xgo/runtime_gen/trap/flags/build_cache.go create mode 100755 cmd/xgo/runtime_gen/trap/flags/flags.go create mode 100644 runtime/test/trap/flags/persistent_after_build/persistent.go create mode 100644 runtime/test/trap/flags/persistent_after_build/persistent_test.go create mode 100644 runtime/test/trap/flags/persistent_after_build/testdata/flags_test.go create mode 100644 runtime/test/trap/flags/persistent_after_build/testdata/go.mod create mode 100644 runtime/trap/flags/build_cache.go create mode 100644 runtime/trap/flags/flags.go create mode 100644 support/transform/patch/format/format.go create mode 100644 support/transform/patch/unpatch/testdata/duplicate/expect.go create mode 100644 support/transform/patch/unpatch/testdata/duplicate/original.go create mode 100644 support/transform/patch/unpatch/testdata/missing_close/original.go create mode 100644 support/transform/patch/unpatch/testdata/missing_end/original.go create mode 100644 support/transform/patch/unpatch/testdata/missing_id/original.go create mode 100644 support/transform/patch/unpatch/testdata/recover_replaced/expect.go create mode 100644 support/transform/patch/unpatch/testdata/recover_replaced/original.go create mode 100644 support/transform/patch/unpatch/testdata/simple/expect.go create mode 100644 support/transform/patch/unpatch/testdata/simple/original.go create mode 100644 support/transform/patch/unpatch/unpatch.go create mode 100644 support/transform/patch/unpatch/unpatch_test.go diff --git a/.github/workflows/go-compatible.yml b/.github/workflows/go-compatible.yml index 9c2c1eb0..1a4e33e8 100644 --- a/.github/workflows/go-compatible.yml +++ b/.github/workflows/go-compatible.yml @@ -26,4 +26,4 @@ jobs: go-version: '${{ matrix.go }}' - name: Test - run: go run ./script/run-test --reset-instrument --debug -v \ No newline at end of file + run: go run ./script/run-test --install-xgo --reset-instrument --debug -v \ No newline at end of file diff --git a/.github/workflows/go-next.yml b/.github/workflows/go-next.yml index 31742e78..0e50ffa4 100644 --- a/.github/workflows/go-next.yml +++ b/.github/workflows/go-next.yml @@ -24,9 +24,10 @@ jobs: run: | curl -fsSL -o go.tar.gz "https://go.dev/dl/go${{matrix.go}}.linux-amd64.tar.gz" mkdir setup + mkdir setup/gopath tar -C setup -xzf go.tar.gz ls setup GOROOT=$PWD/setup/go PATH=$PWD/setup/go/bin:$PATH go version - name: Test - run: GOROOT=$PWD/setup/go PATH=$PWD/setup/go/bin:$PATH go run ./script/run-test --reset-instrument --debug -v \ No newline at end of file + run: GOROOT=$PWD/setup/go GOPATH=$PWD/setup/gopath PATH=$PWD/setup/go/bin:$PWD/setup/gopath/bin:$PATH go run ./script/run-test --install-xgo --reset-instrument --debug -v \ No newline at end of file diff --git a/.github/workflows/go-windows.yml b/.github/workflows/go-windows.yml index 7e601b21..9a6dddeb 100644 --- a/.github/workflows/go-windows.yml +++ b/.github/workflows/go-windows.yml @@ -26,4 +26,4 @@ jobs: go-version: '${{ matrix.go }}' - name: Test - run: go run ./script/run-test --reset-instrument --debug -v \ No newline at end of file + run: go run ./script/run-test --install-xgo --reset-instrument --debug -v \ No newline at end of file diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 01e22e9f..cce5996e 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -26,7 +26,7 @@ jobs: go-version: '${{ matrix.go }}' - name: Test - run: go run ./script/run-test --reset-instrument --debug -v -cover -coverpkg github.com/xhd2015/xgo/runtime/... -coverprofile cover.out + run: go run ./script/run-test --install-xgo --reset-instrument --debug -v -cover -coverpkg github.com/xhd2015/xgo/runtime/... -coverprofile cover.out - name: Merge Coverages run: go run ./cmd/go-tool-coverage merge ./cover-runtime.out ./cover-runtime-sub.out -o cover-runtime-merged.out --exclude-prefix github.com/xhd2015/xgo/runtime/test diff --git a/cmd/xgo/exec_tool/debug.go b/cmd/xgo/exec_tool/debug.go index ed6cf5ac..d506a006 100644 --- a/cmd/xgo/exec_tool/debug.go +++ b/cmd/xgo/exec_tool/debug.go @@ -46,6 +46,11 @@ func getDebugEnvList(xgoCompilerEnableEnv string) [][2]string { {XGO_DEBUG_DUMP_AST_FILE, os.Getenv(XGO_DEBUG_DUMP_AST_FILE)}, {XGO_MAIN_MODULE, os.Getenv(XGO_MAIN_MODULE)}, {XGO_COMPILE_PKG_DATA_DIR, os.Getenv(XGO_COMPILE_PKG_DATA_DIR)}, + + // strace + {XGO_STACK_TRACE, os.Getenv(XGO_STACK_TRACE)}, + {XGO_STACK_TRACE_DIR, os.Getenv(XGO_STACK_TRACE_DIR)}, + {XGO_STD_LIB_TRAP_DEFAULT_ALLOW, os.Getenv(XGO_STD_LIB_TRAP_DEFAULT_ALLOW)}, {XGO_DEBUG_COMPILE_PKG, os.Getenv(XGO_DEBUG_COMPILE_PKG)}, {XGO_DEBUG_COMPILE_LOG_FILE, os.Getenv(XGO_DEBUG_COMPILE_LOG_FILE)}, diff --git a/cmd/xgo/exec_tool/env.go b/cmd/xgo/exec_tool/env.go index 20bf9964..68d7e207 100644 --- a/cmd/xgo/exec_tool/env.go +++ b/cmd/xgo/exec_tool/env.go @@ -14,6 +14,12 @@ const XGO_TOOLCHAIN_VERSION_NUMBER = "XGO_TOOLCHAIN_VERSION_NUMBER" const XGO_MAIN_MODULE = "XGO_MAIN_MODULE" +// --strace +const XGO_STACK_TRACE = "XGO_STACK_TRACE" + +// --strace-dir +const XGO_STACK_TRACE_DIR = "XGO_STACK_TRACE_DIR" + const XGO_COMPILE_PKG_DATA_DIR = "XGO_COMPILE_PKG_DATA_DIR" const XGO_STD_LIB_TRAP_DEFAULT_ALLOW = "XGO_STD_LIB_TRAP_DEFAULT_ALLOW" diff --git a/cmd/xgo/main.go b/cmd/xgo/main.go index e4f806be..3c94358c 100644 --- a/cmd/xgo/main.go +++ b/cmd/xgo/main.go @@ -165,6 +165,7 @@ func handleBuild(cmd string, args []string) error { dumpIR := opts.dumpIR dumpAST := opts.dumpAST stackTrace := opts.stackTrace + stackTraceDir := opts.stackTraceDir trapStdlib := opts.trapStdlib if cmdExec && len(remainArgs) == 0 { @@ -272,6 +273,22 @@ func handleBuild(cmd string, args []string) error { h.Write(optionsFromFileContent) buildCacheSuffix += "-" + hex.EncodeToString(h.Sum(nil)) } + if stackTrace == "on" || stackTrace == "true" { + v := stackTrace + if v == "true" { + v = "on" + } else if v == "false" { + v = "off" + } + buildCacheSuffix += "-strace_" + v + } + if stackTraceDir != "" && stackTraceDir != "." && stackTraceDir != "./" { + // this affects the flags package + h := md5.New() + h.Write([]byte(stackTraceDir)) + buildCacheSuffix += "-" + hex.EncodeToString(h.Sum(nil)) + } + buildCacheDir := filepath.Join(instrumentDir, "build-cache"+buildCacheSuffix) revisionFile := filepath.Join(instrumentDir, "xgo-revision.txt") fullSyncRecord := filepath.Join(instrumentDir, "full-sync-record.txt") @@ -601,7 +618,7 @@ func handleBuild(cmd string, args []string) error { if vscodeDebugFile != "" { xgoDebugVscode = vscodeDebugFile + vscodeDebugFileSuffix } - execCmd.Env = append(execCmd.Env, "XGO_DEBUG_VSCODE="+xgoDebugVscode) + execCmd.Env = append(execCmd.Env, exec_tool.XGO_DEBUG_VSCODE+"="+xgoDebugVscode) // debug compile package execCmd.Env = append(execCmd.Env, exec_tool.XGO_DEBUG_COMPILE_PKG+"="+debugCompilePkg) @@ -609,7 +626,10 @@ func handleBuild(cmd string, args []string) error { // stack trace if stackTrace != "" { - execCmd.Env = append(execCmd.Env, "XGO_STACK_TRACE="+stackTrace) + execCmd.Env = append(execCmd.Env, exec_tool.XGO_STACK_TRACE+"="+stackTrace) + } + if stackTraceDir != "" { + execCmd.Env = append(execCmd.Env, exec_tool.XGO_STACK_TRACE_DIR+"="+stackTraceDir) } // trap stdlib @@ -617,7 +637,7 @@ func handleBuild(cmd string, args []string) error { if trapStdlib { trapStdlibEnv = "true" } - execCmd.Env = append(execCmd.Env, "XGO_STD_LIB_TRAP_DEFAULT_ALLOW="+trapStdlibEnv) + execCmd.Env = append(execCmd.Env, exec_tool.XGO_STD_LIB_TRAP_DEFAULT_ALLOW+"="+trapStdlibEnv) // compiler options (make abs) var absOptionsFromFile string diff --git a/cmd/xgo/option.go b/cmd/xgo/option.go index ea80da74..4c88fbe5 100644 --- a/cmd/xgo/option.go +++ b/cmd/xgo/option.go @@ -70,7 +70,13 @@ type options struct { // --strace, --strace=on, --strace=off // --stack-stackTrace, --stack-stackTrace=off, --stack-stackTrace=on // to be used in test mode + // the parsed value is either on or off, mapping: + // "",true, on => on + // false, off => off + // other => error stackTrace string + // --strace-dir + stackTraceDir string remainArgs []string @@ -120,6 +126,7 @@ func parseOptions(cmd string, args []string) (*options, error) { var overlay string var modfile string var stackTrace string + var stackTraceDir string var trapStdlib bool var remainArgs []string @@ -314,6 +321,22 @@ func parseOptions(cmd string, args []string) (*options, error) { continue } + // strace dir + stackTraceDirVal, ok, err := tryParseRequiredValue("--strace-dir", args, &i) + if err != nil { + return nil, err + } + if !ok { + stackTraceDirVal, ok, err = tryParseRequiredValue("--stack-trace-dir", args, &i) + if err != nil { + return nil, err + } + } + if ok { + stackTraceDir = stackTraceDirVal + continue + } + argVal, ok := parseStackTraceFlag(arg) if ok { stackTrace = argVal @@ -419,18 +442,20 @@ func parseOptions(cmd string, args []string) (*options, error) { // default true syncWithLink: syncWithLink == nil || *syncWithLink, - mod: mod, - gcflags: gcflags, - overlay: overlay, - modfile: modfile, - stackTrace: stackTrace, - trapStdlib: trapStdlib, + mod: mod, + gcflags: gcflags, + overlay: overlay, + modfile: modfile, + stackTrace: stackTrace, + stackTraceDir: stackTraceDir, + trapStdlib: trapStdlib, remainArgs: remainArgs, testArgs: testArgs, }, nil } +// parse: --opt=x, --opt x, --opt, but not --opt -x func tryParseOption(flag string, args []string, i *int) (string, bool) { v, j, ok := tryParseOptionalValue(flag, args, *i) if !ok { @@ -440,23 +465,49 @@ func tryParseOption(flag string, args []string, i *int) (string, bool) { return v, true } -// parse: --opt=x, --opt x, but not --opt -x +// parse: --opt=x, --opt x, even --opt -x +func tryParseRequiredValue(flag string, args []string, i *int) (string, bool, error) { + v, j, ok, err := tryParseValue(flag, args, *i, false) + if err != nil { + return "", false, err + } + if !ok { + return "", false, nil + } + *i = j + return v, true, nil +} + func tryParseOptionalValue(flag string, args []string, i int) (string, int, bool) { + val, i, ok, err := tryParseValue(flag, args, i, true) + if err != nil { + panic(err) + } + return val, i, ok +} + +func tryParseValue(flag string, args []string, i int, optional bool) (string, int, bool, error) { arg := args[i] if !strings.HasPrefix(arg, flag) { - return "", i, false + return "", i, false, nil } - suffix := strings.TrimPrefix(arg, flag) + suffix := arg[len(flag):] if suffix == "" { - if i >= len(args) || strings.HasPrefix(args[i], "-") { - return "", i, true + if optional { + if i >= len(args) || strings.HasPrefix(args[i], "-") { + return "", i, true, nil + } + } else { + if i >= len(args) { + return "", i, false, fmt.Errorf("%s: requires value", flag) + } } - return args[i+1], i + 1, true + return args[i+1], i + 1, true, nil } if !strings.HasPrefix(suffix, "=") { - return "", i, false + return "", i, false, nil } - return suffix[1:], i, true + return suffix[1:], i, true, nil } func parseStackTraceFlag(arg string) (string, bool) { @@ -476,10 +527,10 @@ func parseStackTraceFlag(arg string) (string, bool) { return "", false } val := arg[len(stackTracePrefix)+1:] - if val == "" || val == "on" { + if val == "" || val == "on" || val == "true" { return "on", true } - if val == "off" { + if val == "off" || val == "false" { return "off", true } panic(fmt.Errorf("unrecognized value %s: %s, expects on|off", arg, val)) diff --git a/cmd/xgo/runtime_gen/trace/trace.go b/cmd/xgo/runtime_gen/trace/trace.go index e6bdbe82..a7dab283 100755 --- a/cmd/xgo/runtime_gen/trace/trace.go +++ b/cmd/xgo/runtime_gen/trace/trace.go @@ -13,28 +13,17 @@ import ( "github.com/xhd2015/xgo/runtime/core" "github.com/xhd2015/xgo/runtime/trap" + "github.com/xhd2015/xgo/runtime/trap/flags" ) // hold goroutine stacks, keyed by goroutine ptr var stackMap sync.Map // uintptr(goroutine) -> *Root var testInfoMapping sync.Map // uintptr(goroutine) -> *testInfo -// persist the --strace flag when invoking xgo test -// stack trace options: -// -// on: automatically collect when test starts and ends -const __xgo_injected_StraceFlag = "" - -// options: -// -// true: stdlib is by default allowed -const __xgo_injected_StdlibTrapDefaultAllow = "" - -var skipStdlibTraceByDefault = __xgo_injected_StdlibTrapDefaultAllow == "true" +var skipStdlibTraceByDefault = flags.TRAP_STDLIB == "true" type testInfo struct { - name string - + name string onFinish func() } @@ -49,7 +38,7 @@ func init() { name: name, } testInfoMapping.LoadOrStore(key, tInfo) - if __xgo_injected_StraceFlag == "on" { + if flags.STRACE == "on" || flags.STRACE == "true" { tInfo.onFinish = Begin() } }) @@ -512,7 +501,20 @@ func fmtStack(root *Root, opts *ExportOptions) (data []byte, err error) { } func emitTraceNoErr(name string, root *Root, opts *ExportOptions) { - emitTrace(name, root, opts) + var err error + defer func() { + if e := recover(); e != nil { + if pe, ok := e.(error); ok { + err = pe + } else { + err = fmt.Errorf("panic: %v", e) + } + } + if err != nil { + fmt.Fprintf(os.Stderr, "emit trace: name=%s %v", name, err) + } + }() + err = emitTrace(name, root, opts) } func formatTime(t time.Time, layout string) (output string) { @@ -532,6 +534,7 @@ func emitTrace(name string, root *Root, opts *ExportOptions) error { } useStdout := xgoTraceOutput == "stdout" subName := name + canUseFlagDir := true if name == "" { traceIDNum := int64(1) ghex := fmt.Sprintf("g_%x", __xgo_link_getcurg()) @@ -542,6 +545,7 @@ func emitTrace(name string, root *Root, opts *ExportOptions) error { } else if useStdout { subName = fmt.Sprintf("%s/%s", ghex, traceID) } else { + canUseFlagDir = false subName = filepath.Join(xgoTraceOutput, ghex, traceID) } } @@ -563,11 +567,17 @@ func emitTrace(name string, root *Root, opts *ExportOptions) error { } subFile := subName + ".json" - subDir := filepath.Dir(subFile) - err := os.MkdirAll(subDir, 0755) - if err != nil { - return err + if canUseFlagDir && flags.STRACE_DIR != "" { + subFile = filepath.Join(flags.STRACE_DIR, subFile) + } else { + subDir := filepath.Dir(subFile) + err := os.MkdirAll(subDir, 0755) + if err != nil { + return err + } } + + var err error trap.Direct(func() { err = WriteFile(subFile, traceOut, 0755) }) diff --git a/cmd/xgo/runtime_gen/trap/flags/build_cache.go b/cmd/xgo/runtime_gen/trap/flags/build_cache.go new file mode 100755 index 00000000..c85f7f74 --- /dev/null +++ b/cmd/xgo/runtime_gen/trap/flags/build_cache.go @@ -0,0 +1,5 @@ +package flags + +// if any flag changed, this file should be replaced to +// cause rebuild +const _ = "__BUILD_CACHE__" diff --git a/cmd/xgo/runtime_gen/trap/flags/flags.go b/cmd/xgo/runtime_gen/trap/flags/flags.go new file mode 100755 index 00000000..b3aaf24e --- /dev/null +++ b/cmd/xgo/runtime_gen/trap/flags/flags.go @@ -0,0 +1,31 @@ +package flags + +// flag: --strace +// env: XGO_STACK_TRACE +// description: +// +// persist the --strace flag when invoking xgo test, +// if the value is on or true, trace will be automatically +// collected when test starts and ends +// +// values: +// +// on,true => --strace, --strace=on, --strace=true +// off,false,empty string => --strace=off, --strace=false +const STRACE = "" + +// flag: --strace-dir +// env: XGO_STACK_TRACE_DIR +// values: +// +// directory, default current dir +const STRACE_DIR = "" + +// flag: --trap-stdlib +// env: XGO_STD_LIB_TRAP_DEFAULT_ALLOW +// description: if true, stdlib trap is by default allowed +// values: +// +// true - default with test +// empty string and any other value - --trap-stdlib=false +const TRAP_STDLIB = "" diff --git a/cmd/xgo/version.go b/cmd/xgo/version.go index 3a2ec466..e9e9cb67 100644 --- a/cmd/xgo/version.go +++ b/cmd/xgo/version.go @@ -4,8 +4,8 @@ import "fmt" // auto updated const VERSION = "1.0.47" -const REVISION = "2b5b421d4069bcde6b70e76b157e98e39634672c+1" -const NUMBER = 301 +const REVISION = "6bd25c22e0175d3a73dde9e55151b32fbf80c67d+1" +const NUMBER = 302 // manually updated const CORE_VERSION = "1.0.47" diff --git a/patch/ctxt/ctx.go b/patch/ctxt/ctx.go index 0f0c9e43..c9808935 100644 --- a/patch/ctxt/ctx.go +++ b/patch/ctxt/ctx.go @@ -9,6 +9,7 @@ const XgoModule = "github.com/xhd2015/xgo" const XgoRuntimePkg = XgoModule + "/runtime" const XgoRuntimeCorePkg = XgoModule + "/runtime/core" const XgoRuntimeTracePkg = XgoModule + "/runtime/trace" +const XgoRuntimeFlagsPkg = XgoRuntimePkg + "/trap/flags" const XgoLinkTrapVarForGenerated = "__xgo_link_trap_var_for_generated" diff --git a/patch/syntax/syntax.go b/patch/syntax/syntax.go index 1fe22559..0ad01b2b 100644 --- a/patch/syntax/syntax.go +++ b/patch/syntax/syntax.go @@ -32,10 +32,22 @@ const XGO_NUMBER = "XGO_NUMBER" // --strace const XGO_STACK_TRACE = "XGO_STACK_TRACE" +const XGO_STACK_TRACE_DIR = "XGO_STACK_TRACE_DIR" const XGO_STD_LIB_TRAP_DEFAULT_ALLOW = "XGO_STD_LIB_TRAP_DEFAULT_ALLOW" + +// Deprecated: use flag_STRACE,.. instead const straceFlagConstName = "__xgo_injected_StraceFlag" const trapStdlibFlagConstName = "__xgo_injected_StdlibTrapDefaultAllow" +const ( + // --strace + flag_STRACE = "STRACE" + // --strace-dir + flag_STRACE_DIR = "STRACE_DIR" + // --trap-stdlib + flag_TRAP_STDLIB = "TRAP_STDLIB" +) + // this link function is considered safe as we do not allow user // to define such one,there will be no abuse const XgoLinkGeneratedRegisterFunc = "__xgo_link_generated_register_func" @@ -335,9 +347,12 @@ func registerAndTrapFuncs(fileList []*syntax.File, addFile func(name string, r i func injectXgoFlags(fileList []*syntax.File) { pkgPath := xgo_ctxt.GetPkgPath() switch pkgPath { + case xgo_ctxt.XgoRuntimeFlagsPkg: + injectXgoGeneralFlags(fileList) case xgo_ctxt.XgoRuntimeCorePkg: patchXgoRuntimeCoreVersions(fileList) case xgo_ctxt.XgoRuntimeTracePkg: + // Deprecated, left here only for compatible purpose injectXgoStraceFlag(fileList) } } @@ -408,6 +423,7 @@ func forEachConst(declList []syntax.Decl, fn func(constDecl *syntax.ConstDecl) b } } +// Deprecated: use injectXgoGeneralFlags instead func injectXgoStraceFlag(fileList []*syntax.File) { straceFlag := os.Getenv(XGO_STACK_TRACE) trapStdlibFlag := os.Getenv(XGO_STD_LIB_TRAP_DEFAULT_ALLOW) @@ -433,6 +449,24 @@ func injectXgoStraceFlag(fileList []*syntax.File) { }) } +func injectXgoGeneralFlags(fileList []*syntax.File) { + for _, file := range fileList { + forEachConst(file.DeclList, func(constDecl *syntax.ConstDecl) bool { + for _, name := range constDecl.NameList { + switch name.Value { + case flag_STRACE: + constDecl.Values = newStringLit(os.Getenv(XGO_STACK_TRACE)) + case flag_STRACE_DIR: + constDecl.Values = newStringLit(os.Getenv(XGO_STACK_TRACE_DIR)) + case flag_TRAP_STDLIB: + constDecl.Values = newStringLit(os.Getenv(XGO_STD_LIB_TRAP_DEFAULT_ALLOW)) + } + } + return false + }) + } +} + func getFileIndexMapping(files []*syntax.File) map[*syntax.File]int { m := make(map[*syntax.File]int, len(files)) for i, file := range files { diff --git a/runtime/test/trap/flags/persistent_after_build/persistent.go b/runtime/test/trap/flags/persistent_after_build/persistent.go new file mode 100644 index 00000000..a344a4e5 --- /dev/null +++ b/runtime/test/trap/flags/persistent_after_build/persistent.go @@ -0,0 +1 @@ +package persistent_after_build diff --git a/runtime/test/trap/flags/persistent_after_build/persistent_test.go b/runtime/test/trap/flags/persistent_after_build/persistent_test.go new file mode 100644 index 00000000..559752e9 --- /dev/null +++ b/runtime/test/trap/flags/persistent_after_build/persistent_test.go @@ -0,0 +1,40 @@ +//go:build unix +// +build unix + +package persistent_after_build + +import ( + "os" + "strings" + "testing" + + "github.com/xhd2015/xgo/support/cmd" +) + +func TestPersistentAfterBuild(t *testing.T) { + var xgoCmd string + var args []string + + testCmd := os.Getenv("XGO_TEST_COMMAND") + if testCmd != "" { + cmds := strings.Split(testCmd, " ") + xgoCmd = cmds[0] + args = cmds[1:] + } else { + xgoCmd = "xgo" + } + + args = append(args, "test", "-c", "-o", "test.bin", "--strace", "--strace-dir", "/tmp") + + // build + err := cmd.Debug().Dir("./testdata").Run(xgoCmd, args...) + if err != nil { + t.Error(err) + return + } + err = cmd.Debug().Dir("./testdata").Run("./test.bin", "-test.v") + if err != nil { + t.Error(err) + return + } +} diff --git a/runtime/test/trap/flags/persistent_after_build/testdata/flags_test.go b/runtime/test/trap/flags/persistent_after_build/testdata/flags_test.go new file mode 100644 index 00000000..9b8ef19b --- /dev/null +++ b/runtime/test/trap/flags/persistent_after_build/testdata/flags_test.go @@ -0,0 +1,23 @@ +package main + +import ( + "testing" + + "github.com/xhd2015/xgo/runtime/trap/flags" +) + +// go run -tags dev ./cmd/xgo test -v --project-dir runtime/test/trap/flags/persistent_after_build/testdata --strace --strace-dir /tmp ./ + +// separate: +// +// go run -tags dev ./cmd/xgo test -c --project-dir runtime/test/trap/flags/persistent_after_build/testdata --strace --strace-dir /tmp -o test.bin +// +// ./test.bin -test.v +func TestFlags(t *testing.T) { + if flags.STRACE != "on" { + t.Errorf("STRACE expect: %s, actual: %s", "on", flags.STRACE) + } + if flags.STRACE_DIR != "/tmp" { + t.Errorf("STRACE_DIR expect: %s, actual: %s", "/tmp", flags.STRACE_DIR) + } +} diff --git a/runtime/test/trap/flags/persistent_after_build/testdata/go.mod b/runtime/test/trap/flags/persistent_after_build/testdata/go.mod new file mode 100644 index 00000000..53c811a4 --- /dev/null +++ b/runtime/test/trap/flags/persistent_after_build/testdata/go.mod @@ -0,0 +1,7 @@ +module github.com/xhd2015/xgo/runtime/test/trap/flags/persistent_after_build/testdata + +go 1.14 + +require github.com/xhd2015/xgo/runtime v1.0.0 + +replace github.com/xhd2015/xgo/runtime => ../../../../.. diff --git a/runtime/trace/trace.go b/runtime/trace/trace.go index e6bdbe82..a7dab283 100644 --- a/runtime/trace/trace.go +++ b/runtime/trace/trace.go @@ -13,28 +13,17 @@ import ( "github.com/xhd2015/xgo/runtime/core" "github.com/xhd2015/xgo/runtime/trap" + "github.com/xhd2015/xgo/runtime/trap/flags" ) // hold goroutine stacks, keyed by goroutine ptr var stackMap sync.Map // uintptr(goroutine) -> *Root var testInfoMapping sync.Map // uintptr(goroutine) -> *testInfo -// persist the --strace flag when invoking xgo test -// stack trace options: -// -// on: automatically collect when test starts and ends -const __xgo_injected_StraceFlag = "" - -// options: -// -// true: stdlib is by default allowed -const __xgo_injected_StdlibTrapDefaultAllow = "" - -var skipStdlibTraceByDefault = __xgo_injected_StdlibTrapDefaultAllow == "true" +var skipStdlibTraceByDefault = flags.TRAP_STDLIB == "true" type testInfo struct { - name string - + name string onFinish func() } @@ -49,7 +38,7 @@ func init() { name: name, } testInfoMapping.LoadOrStore(key, tInfo) - if __xgo_injected_StraceFlag == "on" { + if flags.STRACE == "on" || flags.STRACE == "true" { tInfo.onFinish = Begin() } }) @@ -512,7 +501,20 @@ func fmtStack(root *Root, opts *ExportOptions) (data []byte, err error) { } func emitTraceNoErr(name string, root *Root, opts *ExportOptions) { - emitTrace(name, root, opts) + var err error + defer func() { + if e := recover(); e != nil { + if pe, ok := e.(error); ok { + err = pe + } else { + err = fmt.Errorf("panic: %v", e) + } + } + if err != nil { + fmt.Fprintf(os.Stderr, "emit trace: name=%s %v", name, err) + } + }() + err = emitTrace(name, root, opts) } func formatTime(t time.Time, layout string) (output string) { @@ -532,6 +534,7 @@ func emitTrace(name string, root *Root, opts *ExportOptions) error { } useStdout := xgoTraceOutput == "stdout" subName := name + canUseFlagDir := true if name == "" { traceIDNum := int64(1) ghex := fmt.Sprintf("g_%x", __xgo_link_getcurg()) @@ -542,6 +545,7 @@ func emitTrace(name string, root *Root, opts *ExportOptions) error { } else if useStdout { subName = fmt.Sprintf("%s/%s", ghex, traceID) } else { + canUseFlagDir = false subName = filepath.Join(xgoTraceOutput, ghex, traceID) } } @@ -563,11 +567,17 @@ func emitTrace(name string, root *Root, opts *ExportOptions) error { } subFile := subName + ".json" - subDir := filepath.Dir(subFile) - err := os.MkdirAll(subDir, 0755) - if err != nil { - return err + if canUseFlagDir && flags.STRACE_DIR != "" { + subFile = filepath.Join(flags.STRACE_DIR, subFile) + } else { + subDir := filepath.Dir(subFile) + err := os.MkdirAll(subDir, 0755) + if err != nil { + return err + } } + + var err error trap.Direct(func() { err = WriteFile(subFile, traceOut, 0755) }) diff --git a/runtime/trap/flags/build_cache.go b/runtime/trap/flags/build_cache.go new file mode 100644 index 00000000..c85f7f74 --- /dev/null +++ b/runtime/trap/flags/build_cache.go @@ -0,0 +1,5 @@ +package flags + +// if any flag changed, this file should be replaced to +// cause rebuild +const _ = "__BUILD_CACHE__" diff --git a/runtime/trap/flags/flags.go b/runtime/trap/flags/flags.go new file mode 100644 index 00000000..b3aaf24e --- /dev/null +++ b/runtime/trap/flags/flags.go @@ -0,0 +1,31 @@ +package flags + +// flag: --strace +// env: XGO_STACK_TRACE +// description: +// +// persist the --strace flag when invoking xgo test, +// if the value is on or true, trace will be automatically +// collected when test starts and ends +// +// values: +// +// on,true => --strace, --strace=on, --strace=true +// off,false,empty string => --strace=off, --strace=false +const STRACE = "" + +// flag: --strace-dir +// env: XGO_STACK_TRACE_DIR +// values: +// +// directory, default current dir +const STRACE_DIR = "" + +// flag: --trap-stdlib +// env: XGO_STD_LIB_TRAP_DEFAULT_ALLOW +// description: if true, stdlib trap is by default allowed +// values: +// +// true - default with test +// empty string and any other value - --trap-stdlib=false +const TRAP_STDLIB = "" diff --git a/script/run-test/main.go b/script/run-test/main.go index b19f9e21..580ca56d 100644 --- a/script/run-test/main.go +++ b/script/run-test/main.go @@ -93,6 +93,11 @@ var extraSubTests = []*TestCase{ flags: []string{"--strace"}, windowsFlags: []string{"--trap-stdlib=false", "--strace"}, }, + // trap + { + name: "trap_flags_persistent", + dir: "runtime/test/trap/flags/persistent_after_build", + }, { name: "trap_with_overlay", dir: "runtime/test/trap_with_overlay", @@ -207,6 +212,8 @@ func main() { var xgoRuntimeSubTestOnly bool var xgoDefaultTestOnly bool + var installXgo bool + var debug bool var cover bool var coverpkgs []string @@ -292,6 +299,10 @@ func main() { debug = true continue } + if arg == "--install-xgo" { + installXgo = true + continue + } if arg == "--" { if i+1 < n { remainArgs = append(remainArgs, args[i+1:]...) @@ -366,6 +377,14 @@ func main() { fmt.Fprintf(os.Stderr, "no go select\n") os.Exit(1) } + + if installXgo { + err := cmd.Run("go", "install", "./cmd/xgo") + if err != nil { + fmt.Fprintf(os.Stderr, "install xgo: %v\n", err) + os.Exit(1) + } + } for _, goroot := range goroots { begin := time.Now() fmt.Fprintf(os.Stdout, "TEST %s\n", goroot) @@ -703,7 +722,6 @@ func doRunTest(goroot string, kind testKind, usePlainGo bool, dir string, args [ testArgs = append(testArgs, globalFlags...) testArgs = append(testArgs, args...) testArgs = append(testArgs, tests...) - } // remove extra xgo flags diff --git a/support/assert/err.go b/support/assert/err.go index 9e20fb36..48077d02 100644 --- a/support/assert/err.go +++ b/support/assert/err.go @@ -7,9 +7,9 @@ func tryDiffError(expected interface{}, actual interface{}) (string, bool) { actualErr, actualOK := actual.(error) if expectedOK != actualOK { if expectedOK { - return fmt.Sprintf("expect: error %s, actual: %T %s", expectedErr.Error(), actual, actual), true + return fmt.Sprintf("expect: error %s, actual: %T %v", expectedErr.Error(), actual, actual), true } - return fmt.Sprintf("expect: %T %s, actual: error %s", expectedErr, expectedErr, actualErr.Error()), true + return fmt.Sprintf("expect: %T %v, actual: error %s", expectedErr, expectedErr, actualErr.Error()), true } if !expectedOK { return "", false diff --git a/support/transform/edit/line/line.go b/support/transform/edit/line/line.go index 51f7b23e..9bff9e9d 100644 --- a/support/transform/edit/line/line.go +++ b/support/transform/edit/line/line.go @@ -3,6 +3,8 @@ package line import ( "fmt" "sort" + + "github.com/xhd2015/xgo/support/transform/patch/format" ) type Edit struct { @@ -10,28 +12,32 @@ type Edit struct { } type edit struct { + id string line int prepend []string append []string replace []string } -func (c *Edit) Prepend(lineNum int, lines []string) { +func (c *Edit) Prepend(lineNum int, id string, lines []string) { c.edits = append(c.edits, &edit{ + id: id, line: lineNum, prepend: lines, }) } -func (c *Edit) Append(lineNum int, lines []string) { +func (c *Edit) Append(lineNum int, id string, lines []string) { c.edits = append(c.edits, &edit{ + id: id, line: lineNum, append: lines, }) } -func (c *Edit) Replace(lineNum int, lines []string) { +func (c *Edit) Replace(lineNum int, id string, lines []string) { c.edits = append(c.edits, &edit{ + id: id, line: lineNum, replace: lines, }) @@ -68,24 +74,45 @@ func (c *Edit) Apply(lines []string) ([]string, error) { } // i -> j are same line for k := i; k < j; k++ { - newLines = append(newLines, c.edits[k].prepend...) + newLines = appendLines(newLines, c.edits[k].id, c.edits[k].prepend) } var hasReplaced bool + var hasReplacedID string for k := i; k < j; k++ { if len(c.edits[k].replace) > 0 { - newLines = append(newLines, c.edits[k].replace...) + newLines = appendLines(newLines, c.edits[k].id, c.edits[k].replace) hasReplaced = true + hasReplacedID = c.edits[k].id } } if !hasReplaced { newLines = append(newLines, lines[cursor-1]) + } else if hasReplacedID != "" { + newLines = appendLines(newLines, hasReplacedID, []string{ + format.REPLACED_BEGIN, + lines[cursor-1], + format.REPLACED_END, + }) } cursor++ for k := i; k < j; k++ { - newLines = append(newLines, c.edits[k].append...) + newLines = appendLines(newLines, c.edits[k].id, c.edits[k].append) } i = j - 1 } newLines = append(newLines, lines[cursor-1:]...) return newLines, nil } + +func appendLines(orig []string, id string, lines []string) []string { + if len(lines) == 0 { + return orig + } + if id == "" { + return append(orig, lines...) + } + orig = append(orig, format.Begin(id)) + orig = append(orig, lines...) + orig = append(orig, format.End(id)) + return orig +} diff --git a/support/transform/patch/format/format.go b/support/transform/patch/format/format.go new file mode 100644 index 00000000..34e9a8d7 --- /dev/null +++ b/support/transform/patch/format/format.go @@ -0,0 +1,17 @@ +package format + +// /**/ ... /**/ +const BEGIN = "/* 0 && cmd.id == "" { + return nil, fmt.Errorf("missing id: %s", cmd.lines[0]) } } return &PatchContent{ - ID: id, - PrependLines: prependCmd.lines, - AppendLines: appendCmd.lines, - ReplaceLine: replaceCmd.lines, + Prepend: &PatchLines{ID: prependCmd.id, Lines: prependCmd.lines}, + Append: &PatchLines{ID: appendCmd.id, Lines: appendCmd.lines}, + Replace: &PatchLines{ID: replaceCmd.id, Lines: replaceCmd.lines}, }, nil } diff --git a/support/transform/patch/patch.go b/support/transform/patch/patch.go index 3db6e1f6..67cfb395 100644 --- a/support/transform/patch/patch.go +++ b/support/transform/patch/patch.go @@ -6,13 +6,19 @@ import ( "go/token" "strings" + "github.com/xhd2015/xgo/support/fileutil" "github.com/xhd2015/xgo/support/goparse" "github.com/xhd2015/xgo/support/transform/astdiff" "github.com/xhd2015/xgo/support/transform/edit/line" + "github.com/xhd2015/xgo/support/transform/patch/unpatch" ) // see https://github.com/xhd2015/xgo/issues/169#issuecomment-2241407305 func Patch(old string, patch string) (string, error) { + old, err := unpatch.Unpatch(old) + if err != nil { + return "", err + } srcAST, srcFset, err := goparse.ParseFileCode("old.go", []byte(old)) if err != nil { return "", err @@ -26,7 +32,17 @@ func Patch(old string, patch string) (string, error) { } func PatchFile(srcFile string, patchFile string) (string, error) { - srcCode, srcAST, srcFset, err := goparse.Parse(srcFile) + srcCode, err := fileutil.ReadFile(srcFile) + if err != nil { + return "", err + } + + unpatchedSrcCode, err := unpatch.Unpatch(string(srcCode)) + if err != nil { + return "", err + } + + srcAST, srcFset, err := goparse.ParseFileCode(srcFile, []byte(unpatchedSrcCode)) if err != nil { return "", err } @@ -36,7 +52,7 @@ func PatchFile(srcFile string, patchFile string) (string, error) { return "", err } - return patchAST(string(srcCode), srcAST, srcFset, string(patchCode), patchCodeAST, patchFset) + return patchAST(unpatchedSrcCode, srcAST, srcFset, string(patchCode), patchCodeAST, patchFset) } func patchAST(srcCode string, srcAST *ast.File, srcFset *token.FileSet, patchCode string, patchAST *ast.File, patchFset *token.FileSet) (string, error) { @@ -249,14 +265,14 @@ func patchNodes(edit *line.Edit, srcFset *token.FileSet, patchMapping map[ast.No } line := srcFset.Position(srcNode.Pos()).Line - if len(patch.AppendLines) > 0 { - edit.Append(line, patch.AppendLines) + if patch.Append != nil && len(patch.Append.Lines) > 0 { + edit.Append(line, patch.Append.ID, patch.Append.Lines) } - if len(patch.PrependLines) > 0 { - edit.Prepend(line, patch.PrependLines) + if patch.Prepend != nil && len(patch.Prepend.Lines) > 0 { + edit.Prepend(line, patch.Prepend.ID, patch.Prepend.Lines) } - if len(patch.ReplaceLine) > 0 { - edit.Replace(line, patch.ReplaceLine) + if patch.Replace != nil && len(patch.Replace.Lines) > 0 { + edit.Replace(line, patch.Replace.ID, patch.Replace.Lines) } } } diff --git a/support/transform/patch/patch_test.go b/support/transform/patch/patch_test.go index e46e6db4..5c710f17 100644 --- a/support/transform/patch/patch_test.go +++ b/support/transform/patch/patch_test.go @@ -15,18 +15,18 @@ func TestPatchFile(t *testing.T) { tests := []struct { dir string }{ - // { - // dir: "./testdata/hello_world", - // }, - // { - // dir: "./testdata/replace", - // }, - // { - // dir: "./testdata/import", - // }, - // { - // dir: "./testdata/const_decl", - // }, + { + dir: "./testdata/hello_world", + }, + { + dir: "./testdata/replace", + }, + { + dir: "./testdata/import", + }, + { + dir: "./testdata/const_decl", + }, { dir: "./testdata/prepend_func", }, @@ -62,9 +62,12 @@ func testPatchDir(t *testing.T, dir string) { return } + // t.Logf("result: %v", result) + // assert they are syntically the same if !astdiff.FileSame(resultCode, expectedCode) { result = strings.TrimSuffix(result, "\n") + expected := strings.TrimSuffix(string(expectedBytes), "\n") if diff := assert.Diff(expected, result); diff != "" { t.Errorf("PatchFile(): %s", diff) diff --git a/support/transform/patch/testdata/const_decl/expected.go b/support/transform/patch/testdata/const_decl/expected.go index b9c9706a..bf406c85 100644 --- a/support/transform/patch/testdata/const_decl/expected.go +++ b/support/transform/patch/testdata/const_decl/expected.go @@ -2,14 +2,19 @@ package main import "fmt" -/**/ +/**/ const a = 100 // this is just a comment -/**/ +/**/ + const _ = 10 + +/**/ const b = 10 +/**/ + func main() { fmt.Println("hello world") } diff --git a/support/transform/patch/testdata/hello_world/expected.go b/support/transform/patch/testdata/hello_world/expected.go index 1c2e3882..228597d1 100644 --- a/support/transform/patch/testdata/hello_world/expected.go +++ b/support/transform/patch/testdata/hello_world/expected.go @@ -4,5 +4,7 @@ import "fmt" func main() { fmt.Printf("hello world\n") + /**/ fmt.Printf("the world is patched\n") + /**/ } diff --git a/support/transform/patch/testdata/import/expected.go b/support/transform/patch/testdata/import/expected.go index 47909d9e..a0dbef47 100644 --- a/support/transform/patch/testdata/import/expected.go +++ b/support/transform/patch/testdata/import/expected.go @@ -1,6 +1,8 @@ package main - -import _ "embed" + +/**/ +import _ "embed" +/**/ import "fmt" diff --git a/support/transform/patch/testdata/prepend_func/expected.go b/support/transform/patch/testdata/prepend_func/expected.go index facdfede..405c6b54 100644 --- a/support/transform/patch/testdata/prepend_func/expected.go +++ b/support/transform/patch/testdata/prepend_func/expected.go @@ -2,8 +2,11 @@ package main import "fmt" +/**/ var a int +/**/ + func main() { fmt.Println("hello world") } diff --git a/support/transform/patch/testdata/replace/expected.go b/support/transform/patch/testdata/replace/expected.go index f285d839..939a9c0a 100644 --- a/support/transform/patch/testdata/replace/expected.go +++ b/support/transform/patch/testdata/replace/expected.go @@ -8,5 +8,12 @@ func main() { // greet func greet(s string) string { - return "patched " + s + /**/ + return "patched " +s + /**/ + /**/ + /* + return "hello " + s + */ + /**/ } diff --git a/support/transform/patch/unpatch/testdata/duplicate/expect.go b/support/transform/patch/unpatch/testdata/duplicate/expect.go new file mode 100644 index 00000000..c0481191 --- /dev/null +++ b/support/transform/patch/unpatch/testdata/duplicate/expect.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("hello world") +} diff --git a/support/transform/patch/unpatch/testdata/duplicate/original.go b/support/transform/patch/unpatch/testdata/duplicate/original.go new file mode 100644 index 00000000..d88334a8 --- /dev/null +++ b/support/transform/patch/unpatch/testdata/duplicate/original.go @@ -0,0 +1,17 @@ +package main + +import "fmt" + +/**/ +const X = 10 + +/**/ + +func main() { + fmt.Println("hello world") +} + +/**/ +const Y = 10 + +/**/ diff --git a/support/transform/patch/unpatch/testdata/missing_close/original.go b/support/transform/patch/unpatch/testdata/missing_close/original.go new file mode 100644 index 00000000..0de3ca04 --- /dev/null +++ b/support/transform/patch/unpatch/testdata/missing_close/original.go @@ -0,0 +1,12 @@ +package main + +import "fmt" + +/**/ + +func main() { + fmt.Println("hello world") +} diff --git a/support/transform/patch/unpatch/testdata/missing_end/original.go b/support/transform/patch/unpatch/testdata/missing_end/original.go new file mode 100644 index 00000000..be236438 --- /dev/null +++ b/support/transform/patch/unpatch/testdata/missing_end/original.go @@ -0,0 +1,12 @@ +package main + +import "fmt" + +/**/ +const X = 10 + +/**/ + +func main() { + fmt.Println("hello world") +} diff --git a/support/transform/patch/unpatch/testdata/missing_id/original.go b/support/transform/patch/unpatch/testdata/missing_id/original.go new file mode 100644 index 00000000..ec0a6efd --- /dev/null +++ b/support/transform/patch/unpatch/testdata/missing_id/original.go @@ -0,0 +1,12 @@ +package main + +import "fmt" + +/**/ +const X = 10 + +/**/ + +func main() { + fmt.Println("hello world") +} diff --git a/support/transform/patch/unpatch/testdata/recover_replaced/expect.go b/support/transform/patch/unpatch/testdata/recover_replaced/expect.go new file mode 100644 index 00000000..4dabff81 --- /dev/null +++ b/support/transform/patch/unpatch/testdata/recover_replaced/expect.go @@ -0,0 +1,9 @@ +package main + +import "fmt" + +const X = 10 * 2 + +func main() { + fmt.Println("hello world") +} diff --git a/support/transform/patch/unpatch/testdata/recover_replaced/original.go b/support/transform/patch/unpatch/testdata/recover_replaced/original.go new file mode 100644 index 00000000..f2baf054 --- /dev/null +++ b/support/transform/patch/unpatch/testdata/recover_replaced/original.go @@ -0,0 +1,19 @@ +package main + +import "fmt" + +/**/ +const X = 10 + +/*const X=10*2*/ + +/**/ + +func main() { + fmt.Println("hello world") +} + +/**/ +const Y = 10 + +/**/ diff --git a/support/transform/patch/unpatch/testdata/simple/expect.go b/support/transform/patch/unpatch/testdata/simple/expect.go new file mode 100644 index 00000000..c0481191 --- /dev/null +++ b/support/transform/patch/unpatch/testdata/simple/expect.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("hello world") +} diff --git a/support/transform/patch/unpatch/testdata/simple/original.go b/support/transform/patch/unpatch/testdata/simple/original.go new file mode 100644 index 00000000..1a1d0f56 --- /dev/null +++ b/support/transform/patch/unpatch/testdata/simple/original.go @@ -0,0 +1,12 @@ +package main + +import "fmt" + +/**/ +const X = 10 + +/**/ + +func main() { + fmt.Println("hello world") +} diff --git a/support/transform/patch/unpatch/unpatch.go b/support/transform/patch/unpatch/unpatch.go new file mode 100644 index 00000000..dfabc6c7 --- /dev/null +++ b/support/transform/patch/unpatch/unpatch.go @@ -0,0 +1,103 @@ +// package unpatch removes all patch segments in a file +// restore a patched file to its original state,then +// apply patch again + +package unpatch + +import ( + "fmt" + "strings" + + "github.com/xhd2015/xgo/support/transform/patch/format" +) + +// format: +// /**/ ... /**/ + +func Unpatch(text string) (string, error) { + var b strings.Builder + b.Grow(len(text)) + + i := 0 + for { + l, r, err := findRange(text, i) + if err != nil { + return "", err + } + if l < 0 { + b.WriteString(text[i:]) + break + } + + repL, repR, err := findReplaced(text, l, r) + if err != nil { + return "", err + } + b.WriteString(text[i:l]) + if repL >= 0 { + b.WriteString(text[repL:repR]) + } + + i = r + } + + return b.String(), nil +} + +func findRange(s string, start int) (int, int, error) { + i := strings.Index(s[start:], format.BEGIN) + if i < 0 { + return -1, -1, nil + } + + left := start + i + + i = left + len(format.BEGIN) + + const COMMENT_CLOSE = "*/" + closeIdx := strings.Index(s[i:], COMMENT_CLOSE) + if closeIdx < 0 { + return 0, 0, fmt.Errorf("missing close for %s", ellipse(s[left:], 20)) + } + if s[i+closeIdx-1] != '>' { + return 0, 0, fmt.Errorf("missing close for %s", ellipse(s[left:], 20)) + } + closeIdx-- + id := s[i : i+closeIdx] + if id == "" { + return 0, 0, fmt.Errorf("missing id for %s", ellipse(s[left:], 20)) + } + i += closeIdx + len(format.CLOSE) + + end := format.END + id + format.CLOSE + endIdx := strings.Index(s[i:], end) + if endIdx < 0 { + return 0, 0, fmt.Errorf("missing %s", end) + } + + right := endIdx + i + len(end) + return left, right, nil +} + +func findReplaced(s string, i int, j int) (int, int, error) { + sub := s[i:j] + left := strings.Index(sub, format.REPLACED_BEGIN) + if left < 0 { + return -1, -1, nil + } + p := left + len(format.REPLACED_BEGIN) + + right := strings.Index(sub[p:], format.REPLACED_END) + if right < 0 { + return -1, -1, fmt.Errorf("missing %s", format.REPLACED_END) + } + + return left + i + len(format.REPLACED_BEGIN), right + p + i, nil +} + +func ellipse(s string, maxLen int) string { + if len(s) <= maxLen { + return s + } + return s[:maxLen] + "..." +} diff --git a/support/transform/patch/unpatch/unpatch_test.go b/support/transform/patch/unpatch/unpatch_test.go new file mode 100644 index 00000000..b3ed744c --- /dev/null +++ b/support/transform/patch/unpatch/unpatch_test.go @@ -0,0 +1,95 @@ +// package unpatch removes all patch segments in a file +// restore a patched file to its original state,then +// apply patch again + +package unpatch + +import ( + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/xhd2015/xgo/support/assert" + "github.com/xhd2015/xgo/support/fileutil" + "github.com/xhd2015/xgo/support/goparse" + "github.com/xhd2015/xgo/support/transform/astdiff" +) + +func TestUnpatch(t *testing.T) { + tests := []struct { + dir string + wantErr error + }{ + { + dir: "simple", + }, + { + dir: "duplicate", + }, + { + dir: "missing_close", + wantErr: fmt.Errorf("missing close for /**/"), + }, + { + dir: "missing_end", + wantErr: fmt.Errorf("missing /**/"), + }, + { + dir: "recover_replaced", + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.dir, func(t *testing.T) { + text, err := fileutil.ReadFile(filepath.Join("./testdata", tt.dir, "original.go")) + if err != nil { + t.Error(err) + return + } + got, err := Unpatch(string(text)) + if tt.wantErr != nil { + if err == nil { + t.Errorf("want err: %v, actual nil", tt.wantErr) + return + } + wantErrMsg := tt.wantErr.Error() + errMsg := err.Error() + if !strings.Contains(errMsg, wantErrMsg) { + t.Errorf("want err: %v, actual %s", wantErrMsg, errMsg) + return + } + return + } + if err != nil { + t.Error(err) + return + } + want, err := fileutil.ReadFile(filepath.Join("./testdata", tt.dir, "expect.go")) + if err != nil { + t.Error(err) + return + } + + gotCode, _, err := goparse.ParseFileCode("result.go", []byte(got)) + if err != nil { + t.Error(err) + return + } + expectedCode, _, err := goparse.ParseFileCode("expected.go", want) + if err != nil { + t.Error(err) + return + } + + if !astdiff.FileSame(expectedCode, gotCode) { + wantStr := string(want) + t.Errorf("Unpatch() = %s", assert.Diff(wantStr, got)) + } + }) + } +}