diff --git a/.github/workflows/gnovm.yml b/.github/workflows/gnovm.yml index 71b03b5ca05..b58aa192f0b 100644 --- a/.github/workflows/gnovm.yml +++ b/.github/workflows/gnovm.yml @@ -72,26 +72,52 @@ jobs: - uses: actions/setup-go@v4 with: go-version: ${{ matrix.goversion }} + coverage-dir: ${{ runner.temp }}/coverage - name: test working-directory: gnovm run: | + mkdir -p ${{ coverage-dir }} + + # setup testing environements variables export GOPATH=$HOME/go - export GOTEST_FLAGS="-v -p 1 -timeout=30m -coverprofile=coverage.out -covermode=atomic" + export GOCOVERDIR_TXTAR=${{ coverage-dir }} + export GOTEST_FLAGS="-v -p 1 -timeout=30m -covermode=atomic -args -test.gocoverdir=${{ coverage-dir }} + + # run target test make ${{ matrix.args }} - uses: actions/upload-artifact@v3 if: ${{ runner.os == 'Linux' && matrix.goversion == '1.21.x' }} with: name: ${{runner.os}}-coverage-gnovm-${{ matrix.args}}-${{matrix.goversion}} - path: ./gnovm/coverage.out + path: /tmp/coverage.out upload-coverage: needs: test runs-on: ubuntu-latest + with: + coverage-data: ${{ runner.temp }}/coverage-raw + coverage-output: ${{ runner.temp }}/coverage-out + coverage-profile: ${{ runner.temp }}/coverage.txt steps: - - name: Download all previous coverage artifacts + - name: Download all previous coverage data artifacts uses: actions/download-artifact@v3 with: - path: ${{ runner.temp }}/coverage + path: ${{ coverage-data }} + - uses: actions/setup-go@v4 + with: + go-version: "1.21.x" + - name: Merge coverages + working-directory: ${{ coverage-data }} + run: | + # create coverage directory list separate by coma. + export COVERAGE_DIRS="$(ls -m | tr -d ' ')" + + # merge all directory coverages + go tool covdata merge -i="$COVERAGE_DIRS" -o "${{ coverage-dir-merged }}" + + # generate coverage profile + go tool covdata textfmt -i=merged -o ${{ coverage-profile }} + - name: Upload combined coverage to Codecov uses: codecov/codecov-action@v3 with: diff --git a/gnovm/Makefile b/gnovm/Makefile index 5fcacf94f62..34e94f88633 100644 --- a/gnovm/Makefile +++ b/gnovm/Makefile @@ -46,15 +46,15 @@ _test.pkg: .PHONY: _test.gnolang _test.gnolang: _test.gnolang.native _test.gnolang.stdlibs _test.gnolang.realm _test.gnolang.pkg0 _test.gnolang.pkg1 _test.gnolang.pkg2 _test.gnolang.other -_test.gnolang.other:; go test $(GOTEST_FLAGS) tests/*.go -run "(TestFileStr|TestSelectors)" -_test.gnolang.realm:; go test $(GOTEST_FLAGS) tests/*.go -run "TestFiles/^zrealm" -_test.gnolang.pkg0:; go test $(GOTEST_FLAGS) tests/*.go -run "TestPackages/(bufio|crypto|encoding|errors|internal|io|math|sort|std|stdshim|strconv|strings|testing|unicode)" -_test.gnolang.pkg1:; go test $(GOTEST_FLAGS) tests/*.go -run "TestPackages/regexp" -_test.gnolang.pkg2:; go test $(GOTEST_FLAGS) tests/*.go -run "TestPackages/bytes" -_test.gnolang.native:; go test $(GOTEST_FLAGS) tests/*.go -test.short -run "TestFilesNative/" -_test.gnolang.stdlibs:; go test $(GOTEST_FLAGS) tests/*.go -test.short -run 'TestFiles$$/' -_test.gnolang.native.sync:; go test $(GOTEST_FLAGS) tests/*.go -test.short -run "TestFilesNative/" --update-golden-tests -_test.gnolang.stdlibs.sync:; go test $(GOTEST_FLAGS) tests/*.go -test.short -run 'TestFiles$$/' --update-golden-tests +_test.gnolang.other:; go test tests/*.go -run "(TestFileStr|TestSelectors)" $(GOTEST_FLAGS) +_test.gnolang.realm:; go test tests/*.go -run "TestFiles/^zrealm" $(GOTEST_FLAGS) +_test.gnolang.pkg0:; go test tests/*.go -run "TestPackages/(bufio|crypto|encoding|errors|internal|io|math|sort|std|stdshim|strconv|strings|testing|unicode)" $(GOTEST_FLAGS) +_test.gnolang.pkg1:; go test tests/*.go -run "TestPackages/regexp" $(GOTEST_FLAGS) +_test.gnolang.pkg2:; go test tests/*.go -run "TestPackages/bytes" $(GOTEST_FLAGS) +_test.gnolang.native:; go test tests/*.go -test.short -run "TestFilesNative/" $(GOTEST_FLAGS) +_test.gnolang.stdlibs:; go test tests/*.go -test.short -run 'TestFiles$$/' $(GOTEST_FLAGS) +_test.gnolang.native.sync:; go test tests/*.go -test.short -run "TestFilesNative/" --update-golden-tests $(GOTEST_FLAGS) +_test.gnolang.stdlibs.sync:; go test tests/*.go -test.short -run 'TestFiles$$/' --update-golden-tests $(GOTEST_FLAGS) ######################################## # Code gen diff --git a/gnovm/cmd/gno/main_test.go b/gnovm/cmd/gno/main_test.go index 8d19a50e814..371cdf913e8 100644 --- a/gnovm/cmd/gno/main_test.go +++ b/gnovm/cmd/gno/main_test.go @@ -5,12 +5,10 @@ import ( "context" "fmt" "os" - "os/exec" "path/filepath" "strings" "testing" - "github.com/rogpeppe/go-internal/testscript" "github.com/stretchr/testify/require" "github.com/gnolang/gno/tm2/pkg/commands" @@ -144,45 +142,3 @@ func testMainCaseRun(t *testing.T, tc []testMainCase) { }) } } - -func setupTestScript(t *testing.T, txtarDir string) testscript.Params { - t.Helper() - // Get root location of github.com/gnolang/gno - goModPath, err := exec.Command("go", "env", "GOMOD").CombinedOutput() - require.NoError(t, err) - rootDir := filepath.Dir(string(goModPath)) - // Build a fresh gno binary in a temp directory - gnoBin := filepath.Join(t.TempDir(), "gno") - err = exec.Command("go", "build", "-o", gnoBin, filepath.Join(rootDir, "gnovm", "cmd", "gno")).Run() - require.NoError(t, err) - // Define script params - return testscript.Params{ - Setup: func(env *testscript.Env) error { - env.Vars = append(env.Vars, - "GNOROOT="+rootDir, // thx PR 1014 :) - // by default, $HOME=/no-home, but we need an existing $HOME directory - // because some commands needs to access $HOME/.cache/go-build - "HOME="+t.TempDir(), - ) - return nil - }, - Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){ - // add a custom "gno" command so txtar files can easily execute "gno" - // without knowing where is the binary or how it is executed. - "gno": func(ts *testscript.TestScript, neg bool, args []string) { - err := ts.Exec(gnoBin, args...) - if err != nil { - ts.Logf("[%v]\n", err) - if !neg { - ts.Fatalf("unexpected gno command failure") - } - } else { - if neg { - ts.Fatalf("unexpected gno command success") - } - } - }, - }, - Dir: txtarDir, - } -} diff --git a/gnovm/cmd/gno/test_test.go b/gnovm/cmd/gno/test_test.go index f5b069a5f03..8c799f910db 100644 --- a/gnovm/cmd/gno/test_test.go +++ b/gnovm/cmd/gno/test_test.go @@ -3,9 +3,20 @@ package main import ( "testing" + "github.com/gnolang/gno/gnovm/pkg/integration" "github.com/rogpeppe/go-internal/testscript" + "github.com/stretchr/testify/require" ) func TestTest(t *testing.T) { - testscript.Run(t, setupTestScript(t, "testdata/gno_test")) + p := testscript.Params{ + Dir: "testdata/gno_test", + } + + err := integration.SetupCoverage(&p) + require.NoError(t, err) + err = integration.SetupGno(&p, t.TempDir()) + require.NoError(t, err) + + testscript.Run(t, p) } diff --git a/gnovm/pkg/integration/coverage.go b/gnovm/pkg/integration/coverage.go new file mode 100644 index 00000000000..acfd5a4fc79 --- /dev/null +++ b/gnovm/pkg/integration/coverage.go @@ -0,0 +1,61 @@ +package integration + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "sync" + "testing" + + "github.com/rogpeppe/go-internal/testscript" +) + +var coverageEnv struct { + coverdir string + once sync.Once +} + +// SetupCoverage sets up the given test environment for coverage +func SetupCoverage(p *testscript.Params) error { + coverdir := os.Getenv("GOCOVERDIR_TXTAR") + if testing.CoverMode() == "" || coverdir == "" { + return nil + } + + var err error + + if !filepath.IsAbs(coverdir) { + coverdir, err = filepath.Abs(coverdir) + if err != nil { + return fmt.Errorf("unable to determine absolute path of %q: %w", coverdir, err) + } + } + + info, err := os.Stat(coverdir) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + if mkErr := os.Mkdir(coverdir, 0o755); mkErr != nil { + return fmt.Errorf("failed to testscripts coverage dir %q: %w", coverdir, mkErr) + } + } else { + // Handle other potential errors from os.Stat + return fmt.Errorf("failed to stat %q: %w", coverdir, err) + } + } else if !info.IsDir() { + return fmt.Errorf("coverage: %q is not a directory", coverdir) + } + + origSetup := p.Setup + p.Setup = func(env *testscript.Env) error { + if origSetup != nil { + origSetup(env) + } + + // override GOCOVEDIR directory + env.Setenv("GOCOVERDIR", coverdir) + return nil + } + + return nil +} diff --git a/gnovm/pkg/integration/integration_gno.go b/gnovm/pkg/integration/integration_gno.go new file mode 100644 index 00000000000..5e0e4b55a9f --- /dev/null +++ b/gnovm/pkg/integration/integration_gno.go @@ -0,0 +1,106 @@ +package integration + +import ( + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "sync" + "testing" + + "github.com/rogpeppe/go-internal/testscript" +) + +var gnoEnv struct { + err error + gnoBin string + once sync.Once +} + +func SetupGno(p *testscript.Params, buildDir string) error { + gnoroot := os.Getenv("GNOROOT") + if gnoroot == "" { + // Get root location of github.com/gnolang/gno + goModPath, err := exec.Command("go", "env", "GOMOD").CombinedOutput() + if err != nil { + return fmt.Errorf("unable to determine gno root directory") + } + + gnoroot = filepath.Dir(string(goModPath)) + } + + info, err := os.Stat(buildDir) + if err != nil { + return fmt.Errorf("unable to stat: %q", buildDir) + } + + if !info.IsDir() { + return fmt.Errorf("given buildDir is not a directory: %q", buildDir) + } + + gnoBin := filepath.Join(buildDir, "gno") + if _, err = os.Stat(gnoBin); errors.Is(err, os.ErrNotExist) { + // Build a fresh gno binary in a temp directory + gnoArgsBuilder := []string{"build", "-o", gnoBin} + + // Add coverage if needed + if coverMode := testing.CoverMode(); coverMode != "" { + gnoArgsBuilder = append(gnoArgsBuilder, "-covermode", coverMode) + } + + // Add target command + gnoArgsBuilder = append(gnoArgsBuilder, filepath.Join(gnoroot, "gnovm", "cmd", "gno")) + + if err = exec.Command("go", gnoArgsBuilder...).Run(); err != nil { + return fmt.Errorf("uanble to build gno binary: %w", err) + } + } else if err != nil { + return err + + } + + // Define setup scripts + origSetup := p.Setup + p.Setup = func(env *testscript.Env) error { + if origSetup != nil { + if err := origSetup(env); err != nil { + return err + } + } + + home, err := os.MkdirTemp("", "gno") + if err != nil { + return fmt.Errorf("unable to create temporary home directory: %w", err) + } + + env.Vars = append(env.Vars, + "GNOROOT="+gnoroot, // thx PR 1014 :) + // by default, $HOME=/no-home, but we need an existing $HOME directory + // because some commands needs to access $HOME/.cache/go-build + "HOME="+home, + ) + + return nil + } + + if p.Cmds == nil { + p.Cmds = make(map[string]func(ts *testscript.TestScript, neg bool, args []string)) + } + + // Set gno command + p.Cmds["gno"] = func(ts *testscript.TestScript, neg bool, args []string) { + err := ts.Exec(gnoBin, args...) + if err != nil { + ts.Logf("[%v]\n", err) + if !neg { + ts.Fatalf("unexpected gno command failure") + } + } else { + if neg { + ts.Fatalf("unexpected gno command success") + } + } + } + return nil +}