Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ check-go-version:
test: check-go-version
@echo "running all tests"
@go fmt ./...
@go run honnef.co/go/tools/cmd/staticcheck@v0.6.1 github.com/cucumber/godog
@go run honnef.co/go/tools/cmd/staticcheck@v0.6.1 github.com/cucumber/godog/cmd/godog
@go run honnef.co/go/tools/cmd/staticcheck@v0.5.1 github.com/cucumber/godog
@go run honnef.co/go/tools/cmd/staticcheck@v0.5.1 github.com/cucumber/godog/cmd/godog
go vet ./...
go test -race ./...
go run ./cmd/godog -f progress -c 4
Expand Down
43 changes: 43 additions & 0 deletions internal/builder/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func Test_GodogBuild(t *testing.T) {
t.Run("WithVendoredGodogWithoutModule", testWithVendoredGodogWithoutModule)
t.Run("WithVendoredGodogAndMod", testWithVendoredGodogAndMod)
t.Run("WithIncorrectProjectStructure", testWithIncorrectProjectStructure)
t.Run("FailsWhenDependencyFileCannotBeCreated", testFailsWhenDependencyFileCannotBeCreated)

t.Run("WithModule", func(t *testing.T) {
t.Run("OutsideGopathAndHavingOnlyFeature", testOutsideGopathAndHavingOnlyFeature)
Expand Down Expand Up @@ -359,3 +360,45 @@ func (bt builderTestCase) run(t *testing.T) {
err = testCmd.Run()
require.Nil(t, err, "stdout:\n%s\nstderr:\n%s", stdout.String(), stderr.String())
}

func testFailsWhenDependencyFileCannotBeCreated(t *testing.T) {
builderTC := builderTestCase{}

// GOPATH sandbox
gopath := filepath.Join(os.TempDir(), t.Name(), "_gp")
defer os.RemoveAll(gopath)

if err := os.Setenv("GOPATH", gopath); err != nil {
t.Fatalf("set GOPATH: %v", err)
}

builderTC.dir = filepath.Join(gopath, "src", "tempfail")
builderTC.files = map[string]string{
"foo.go": `package tempfail
func Foo() {}
`,
"foo_test.go": `package tempfail
import "testing"
func TestFoo(t *testing.T) { Foo() }
`,
}

err := buildTestPackage(builderTC.dir, builderTC.files)
require.Nil(t, err)

// Block the path that Build tries to write
badPath := filepath.Join(builderTC.dir, "godog_dependency_file_test.go")
err = os.MkdirAll(badPath, 0755) // directory → WriteFile will error
require.Nil(t, err)

prevDir, err := os.Getwd()
require.Nil(t, err)

err = os.Chdir(builderTC.dir)
require.Nil(t, err)
defer os.Chdir(prevDir)

err = builder.Build(filepath.Join(builderTC.dir, "suite_bin"))
require.NotNil(t, err)
require.Contains(t, err.Error(), "godog_dependency_file_test.go")
}
124 changes: 124 additions & 0 deletions internal/builder/builder_vendored_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package builder

import (
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
)

// buildStubTool builds a tiny cross-platform binary that simply honours “-o
// <file>” (creating an empty file) and otherwise exits 0. We can use the same
// stub for both `compile` and `link`, allowing `Build` to be executed
// without invoking the real tool-chain.
func buildStubTool(t *testing.T) string {
t.Helper()

dir := t.TempDir()
stubSrc := filepath.Join(dir, "stub.go")
stubBin := filepath.Join(dir, "stub")
if runtime.GOOS == "windows" {
stubBin += ".exe"
}

const program = `package main
import (
"os"
)
func main() {
for i, a := range os.Args {
if a == "-o" && i+1 < len(os.Args) {
f, _ := os.Create(os.Args[i+1])
f.Close()
}
}
}`
if err := os.WriteFile(stubSrc, []byte(program), 0644); err != nil {
t.Fatalf("cannot write stub source: %v", err)
}
cmd := exec.Command("go", "build", "-o", stubBin, stubSrc)
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("failed to build stub tool: %v\n%s", err, string(out))
}
return stubBin
}

// makePackage creates a throw-away Go package (and *_test.go) inside dir.
func makePackage(t *testing.T, dir, name string) {
t.Helper()

if err := os.MkdirAll(dir, 0755); err != nil {
t.Fatalf("cannot create package dir: %v", err)
}
src := `package ` + name + `
func Foo() {}
`
testSrc := `package ` + name + `
import "testing"
func TestFoo(t *testing.T) { Foo() }
`
if err := os.WriteFile(filepath.Join(dir, "foo.go"), []byte(src), 0644); err != nil {
t.Fatalf("write foo.go: %v", err)
}
if err := os.WriteFile(filepath.Join(dir, "foo_test.go"), []byte(testSrc), 0644); err != nil {
t.Fatalf("write foo_test.go: %v", err)
}
}

// We create a vendor copy of godog so that `maybeVendoredGodog()` is non-nil,
// and use stub tools so the heavy compile/link work is skipped.
func TestRewritesImportCfgForVendoredGodog(t *testing.T) {
origWD, _ := os.Getwd()
defer os.Chdir(origWD)

// GOPATH sandbox ----------------------------------------------------------
gopath := t.TempDir()
if err := os.Setenv("GOPATH", gopath); err != nil {
t.Fatalf("set GOPATH: %v", err)
}
gopaths = filepath.SplitList(gopath)

// Disable modules for a classic GOPATH build (avoids 'go mod tidy').
if err := os.Setenv("GO111MODULE", "off"); err != nil {
t.Fatalf("set GO111MODULE: %v", err)
}
defer os.Setenv("GO111MODULE", "on")

pkgDir := filepath.Join(gopath, "src", "vendoredproj")
defer os.Remove(pkgDir)
makePackage(t, pkgDir, "vendoredproj")

// Add a *vendored* copy of godog so maybeVendoredGodog() is triggered.
vendorGodog := filepath.Join(pkgDir, "vendor", "github.com", "cucumber", "godog")
if err := os.MkdirAll(vendorGodog, 0755); err != nil {
t.Fatalf("mkdir vendor godog: %v", err)
}
if err := os.WriteFile(filepath.Join(vendorGodog, "doc.go"),
[]byte("package godog\nconst Version = \"0.0.1\""), 0644); err != nil {
t.Fatalf("write vendor godog/doc.go: %v", err)
}

// -------------------------------------------------------------------------
// Replace compile / link with stub tools
// -------------------------------------------------------------------------
stub := buildStubTool(t)

oldCompile, oldLinker := compiler, linker
compiler, linker = stub, stub
defer func() { compiler, linker = oldCompile, oldLinker }()

if err := os.Chdir(pkgDir); err != nil {
t.Fatalf("chdir: %v", err)
}

bin := filepath.Join(pkgDir, "godog_suite_bin")
defer os.Remove(bin)
if err := Build(bin); err != nil {
t.Fatalf("Build failed: %v", err)
}

if _, err := os.Stat(bin); err != nil {
t.Fatalf("expected binary %s to exist: %v", bin, err)
}
}
2 changes: 1 addition & 1 deletion internal/formatters/fmt_progress.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (f *Progress) Summary() {
left := math.Mod(float64(*f.Steps), float64(f.StepsPerRow))
if left != 0 {
if *f.Steps > f.StepsPerRow {
fmt.Fprintf(f.out, "%s", s(f.StepsPerRow-int(left))+fmt.Sprintf(" %d\n", *f.Steps))
fmt.Fprintf(f.out, "%s %d\n", s(f.StepsPerRow-int(left)), *f.Steps)
} else {
fmt.Fprintf(f.out, " %d\n", *f.Steps)
}
Expand Down
81 changes: 81 additions & 0 deletions internal/formatters/fmt_progress_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package formatters

import (
"bytes"
"github.com/cucumber/godog/internal/storage"
"strings"
"testing"
)

// TestSummaryWhenLessThanOneRow verifies that Summary() prints the simple
// single-row form (“ <steps>\n”) when the total number of executed steps is
// smaller than StepsPerRow.
func TestSummaryWhenLessThanOneRow(t *testing.T) {
var buf bytes.Buffer
p := NewProgress("sample-suite", &buf)

// Pretend we executed 5 steps (default StepsPerRow is 70).
*p.Steps = 5

p.SetStorage(storage.NewStorage())

p.Summary()

got := buf.String()
want := " 5\n" // a single space, the number of steps, newline

if !strings.HasPrefix(got, want) {
t.Fatalf("unexpected summary output\nwant prefix: %q\ngot: %q", want, got)
}
}

// TestSummaryWithMultipleRows verifies the branch that pads with spaces in
// order to align the last line when at least one full row is already printed.
func TestSummaryWithMultipleRows(t *testing.T) {
var buf bytes.Buffer
p := NewProgress("sample-suite", &buf)

// 83 is >70, so one full line (70 chars) would already have been printed
// during execution; Summary() must therefore pad with 70-(83-70)=57 spaces
// before the final “ 83\n”. We only assert on the suffix to avoid
// hard-wiring the exact amount of padding.
*p.Steps = p.StepsPerRow + 13 // 83

p.SetStorage(storage.NewStorage())

p.Summary()

got := buf.String()
stepsIndication := " 83\n"

if !strings.Contains(got, stepsIndication) {
t.Fatalf("summary does not end with expected step count\nwant stepsIndication: %q\ngot: %q", stepsIndication, got)
}
if !strings.Contains(got, s(57)) {
t.Fatalf("summary does contains 57 blank spaces before the number of steps")
}
}

// TestSummaryWhenExactlyOnRowBoundary ensures that nothing is printed by the
// special-case block when the total number of steps is an exact multiple of
// StepsPerRow. In that situation Summary() should NOT start with a leading
// space-line; instead it should go straight to Base.Summary() (whose exact
// output we purposely ignore here).
func TestSummaryWhenExactlyOnRowBoundary(t *testing.T) {
var buf bytes.Buffer
p := NewProgress("sample-suite", &buf)

p.SetStorage(storage.NewStorage())

*p.Steps = p.StepsPerRow // 70

p.Summary()

got := buf.String()

// The first character printed by the special block would be a space.
// Absence of that space tells us the branch was correctly skipped.
if strings.HasPrefix(got, " ") {
t.Fatalf("summary should not start with a space when steps %% StepsPerRow == 0; got %q", got)
}
}
Loading