Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port to Windows #216

Merged
merged 8 commits into from Oct 21, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
44 changes: 42 additions & 2 deletions .circleci/config.yml
@@ -1,6 +1,10 @@
# Golang CircleCI 2.0 configuration file
# Check https://circleci.com/docs/2.0/language-go/ for more details
version: 2
version: 2.1

orbs:
win: circleci/windows@2.2.0

jobs:
test:
docker:
Expand Down Expand Up @@ -48,6 +52,41 @@ jobs:
- store_test_results:
path: /tmp/test-results

test_windows:
executor: win/default

environment:
GOFLAGS: -mod=vendor
TEST_RESULTS: ~/test-results
BAUR_TEST_POSTGRESQL_URL: "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable"

working_directory: ~/baur

steps:
- checkout

- run:
name: Preparing Test Environment
command: |
choco install golang --version=1.15.2 -y
choco install postgresql12 --params '/Password:postgres' -y
mkdir -p $Env:TEST_RESULTS

- run:
name: Run Tests
command: |
$go111module = $Env:GO111MODULE
$Env:GO111MODULE="off"
go get github.com/jstemmer/go-junit-report
$Env:GO111MODULE=$go111module
git config --global user.email "circleci-baurtest@example.com"
git config --global user.name "baur"
try { go test --tags=dbtest -v -timeout 5m .\... | Tee-Object -FilePath $Env:TEST_RESULTS\go-test.out } `
finally { Get-Content -Path $Env:TEST_RESULTS\go-test.out | go-junit-report > $Env:TEST_RESULTS\go-test-report.xml }

- store_test_results:
path: ~\test-results

build:
docker:
- image: circleci/golang:1
Expand All @@ -73,9 +112,10 @@ jobs:
command: golangci-lint run

workflows:
version: 2
version: 2.1
workflow:
jobs:
- build
- test
- test_windows
- static_analysis
7 changes: 6 additions & 1 deletion Makefile
Expand Up @@ -42,6 +42,11 @@ dist/linux_amd64/baur:
$(info * creating $(@D)/baur-linux_amd64-$(VERSION).tar.xz.sha256)
@(cd $(@D) && sha256sum baur-linux_amd64-$(VERSION).tar.xz > baur-linux_amd64-$(VERSION).tar.xz.sha256)

.PHONY: dist/windows_amd64/baur.exe
dist/windows_amd64/baur.exe:
$(info * building $@)
@CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build \
$(BUILDFLAGS) -o "$@" cmd/baur/main.go

.PHONY: dirty_worktree_check
dirty_worktree_check:
Expand All @@ -51,7 +56,7 @@ dirty_worktree_check:
fi

.PHONY: release
release: clean dirty_worktree_check dist/linux_amd64/baur dist/darwin_amd64/baur
release: clean dirty_worktree_check dist/linux_amd64/baur dist/darwin_amd64/baur dist/windows_amd64/baur.exe
@echo
@echo next steps:
@echo - git tag v$(VERSION)
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Expand Up @@ -29,3 +29,5 @@ require (
google.golang.org/protobuf v1.25.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
)

replace golang.org/x/sys => golang.org/x/sys v0.0.0-20190830141801-acfa387b8d69
2 changes: 2 additions & 0 deletions go.sum
Expand Up @@ -450,6 +450,8 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190830141801-acfa387b8d69 h1:Wdn4Yb8d5VrsO3jWgaeSZss09x1VLVBMePDh4VW/xSQ=
golang.org/x/sys v0.0.0-20190830141801-acfa387b8d69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3 h1:7TYNF4UdlohbFwpNH04CoPMp1cHUZgO1Ebq5r2hIjfo=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
3 changes: 2 additions & 1 deletion internal/command/diff_inputs_test.go
Expand Up @@ -5,6 +5,7 @@ package command
import (
"encoding/csv"
"fmt"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -392,7 +393,7 @@ func TestDifferencesOutputWithCorrectState(t *testing.T) {
executeWithoutError(t, diffInputsCmd)

expectedOutput := [][]string{
{"D", "app_one/diff_test.txt", originalDigest.String(), newDigest.String()},
{"D", filepath.FromSlash("app_one/diff_test.txt"), originalDigest.String(), newDigest.String()},
{"-", "string:run_one", "sha384:95e52b4c9863a13d596d34df980988cb78bea9ec3381ba981e1656a84cc1c7456f6830bca0e8931be5f0f48593cb5d06", ""},
{"+", "string:run_two", "", "sha384:f3d5e46502641c5591563a0d3157f19a9739616f07bdb4bbc0285cb0a12bd511c026db94f12c719378a20d0ffe85090e"},
}
Expand Down
2 changes: 1 addition & 1 deletion internal/command/testdata/var_in_include/tasks.toml
Expand Up @@ -2,4 +2,4 @@
include_id = "build"
includes = ["$ROOT/inputs.toml#input"]
name = "build"
command = ["ls", "$APPNAME.txt"]
command = ["more", "$APPNAME.txt"]
18 changes: 16 additions & 2 deletions internal/command/upgrade_configs_test.go
Expand Up @@ -3,6 +3,7 @@ package command
import (
"os"
"path/filepath"
"runtime"
"strings"
"testing"

Expand All @@ -21,9 +22,18 @@ func TestUpgrade(t *testing.T) {

initTest(t)

tempdir := t.TempDir()
// When running on Windows we need to change the working directory back to the original
// working directory so the temporary directory that is used can be deleted
var originalDir string
var err error
if runtime.GOOS == "windows" {
originalDir, err = os.Getwd()
assert.NoError(t, err)
}

gitDir := filepath.Join(tempdir, "git")
tempDir := t.TempDir()

gitDir := filepath.Join(tempDir, "git")

require.NoError(t, os.Chdir("/"))
gittest.Clone(t, gitDir, gitURL, commit)
Expand Down Expand Up @@ -57,4 +67,8 @@ func TestUpgrade(t *testing.T) {
assert.Contains(t, taskIDs, "myredis.build")
assert.Contains(t, taskIDs, "random.build")
assert.Contains(t, taskIDs, "unixtime.build")

if runtime.GOOS == "windows" {
require.NoError(t, os.Chdir(originalDir))
}
}
2 changes: 2 additions & 0 deletions internal/digest/sha384/sha384_test.go
Expand Up @@ -126,6 +126,8 @@ func TestHashingNonExistingFileFails(t *testing.T) {
if err != nil {
t.Fatal("creating tempfile failed:", err.Error())
}
// The file must be closed before it can be deleted on Windows
file.Close()
os.Remove(file.Name())

sha := sha384.New()
Expand Down
39 changes: 34 additions & 5 deletions internal/exec/exec_test.go
@@ -1,13 +1,30 @@
package exec

import (
"fmt"
"runtime"
"testing"
)

func TestEchoStdout(t *testing.T) {
const echoStr = "hello world!"

res, err := Command("echo", "-n", echoStr).Run()
// Windows returns StrOutput with surrounding quotation marks
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The windows echo command prints the string quoted?
Or all output that is returned from running commands via the stdlib exec package is quoted?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have investigated this further and the results are quite interesting.
I created a simple app that runs a few different commands with a couple of different inputs, I then ran this on Windows and Linux.
Here are the results:
Running on Windows
image
Running on Linux
image

Summary:

  • cmd adds quotation marks around the string if it contains spaces
  • PowerShell wraps the output onto a new line at the space if it contains spaces
  • more on Linux outputs headers and footers, Windows doesn't

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so the echo command is probably also a shell builtin command like for bash and the implementation of it in cmd and PowerShell differ.
When commands are invoked without a shell, the output that is returned by the exec package should not be quoted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is my understanding.
Windows doesn't have an echo executable so it must be invoked from a shell. However it does have a more executable and when it is invoked the file contents are not quoted.

var expected string
if runtime.GOOS == "windows" {
expected = fmt.Sprintf("\"%s\"", echoStr)
} else {
expected = echoStr
}

var res *Result
var err error
if runtime.GOOS == "windows" {
res, err = Command("cmd", "/C", "echo", echoStr).Run()
} else {
res, err = Command("echo", "-n", echoStr).Run()
}

if err != nil {
t.Fatal(err)
}
Expand All @@ -16,13 +33,19 @@ func TestEchoStdout(t *testing.T) {
t.Fatalf("cmd exited with code %d, expected 0", res.ExitCode)
}

if res.StrOutput() != echoStr {
t.Errorf("expected output '%s', got '%s'", echoStr, res.StrOutput())
if res.StrOutput() != expected {
t.Errorf("expected output '%s', got '%s'", expected, res.StrOutput())
}
}

func TestCommandFails(t *testing.T) {
res, err := Command("false").Run()
var res *Result
var err error
if runtime.GOOS == "windows" {
res, err = Command("cmd", "/C", "exit", "1").Run()
} else {
res, err = Command("false").Run()
}
if err != nil {
t.Fatal(err)
}
Expand All @@ -37,7 +60,13 @@ func TestCommandFails(t *testing.T) {
}

func TestExpectSuccess(t *testing.T) {
res, err := Command("false").ExpectSuccess().Run()
var res *Result
var err error
if runtime.GOOS == "windows" {
res, err = Command("cmd", "/C", "exit", "1").ExpectSuccess().Run()
} else {
res, err = Command("false").ExpectSuccess().Run()
}
if err == nil {
t.Fatal("Command did not return an error")
}
Expand Down
5 changes: 5 additions & 0 deletions internal/fs/fileglob_test.go
Expand Up @@ -172,6 +172,11 @@ func Test_Resolve(t *testing.T) {
for _, tc := range testcases {
tempdir := t.TempDir()

// The path separators in the test cases are Unix style "/", they need to be converted to "\" when running on Windows
for i := range tc.expectedMatches {
tc.expectedMatches[i] = filepath.FromSlash(tc.expectedMatches[i])
}

if len(tc.dir) != 0 {
err := os.MkdirAll(filepath.Join(tempdir, tc.dir), os.ModePerm)
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions internal/resolve/gosource/gosource_test.go
Expand Up @@ -56,6 +56,8 @@ func TestResolve(t *testing.T) {

for i := range testCfg.ExpectedResults {
testCfg.ExpectedResults[i] = strings.Replace(testCfg.ExpectedResults[i], "$WORKDIR", cwd, -1)
// The path separators in the test config are Unix style "/", they need to be converted to "\" when running on Windows
testCfg.ExpectedResults[i] = filepath.FromSlash(testCfg.ExpectedResults[i])
}

resolvedFiles, err := NewResolver(t.Logf).Resolve(
Expand Down
44 changes: 35 additions & 9 deletions internal/testutils/repotest/repotest.go
Expand Up @@ -5,6 +5,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
"testing"

"github.com/simplesurance/baur/v1"
Expand Down Expand Up @@ -43,16 +44,33 @@ func (r *Repo) CreateSimpleApp(t *testing.T) *cfg.App {

appName := "simpleApp"

buildFile := "build.sh"
if runtime.GOOS == "windows" {
buildFile = "build.bat"
}
checkFile := "check.sh"
if runtime.GOOS == "windows" {
checkFile = "check.bat"
}
buildCommand := []string{"sh", fmt.Sprintf("./%s", buildFile)}
if runtime.GOOS == "windows" {
buildCommand = []string{"cmd", "/C", buildFile}
}
checkCommand := []string{"sh", fmt.Sprintf("./%s", checkFile)}
if runtime.GOOS == "windows" {
checkCommand = []string{"cmd", "/C", checkFile}
}

app := cfg.App{
Name: appName,
Tasks: []*cfg.Task{
{
Name: "build",
Command: []string{"sh", "./build.sh"},
Command: buildCommand,
Input: cfg.Input{
Files: []cfg.FileInputs{
{
Paths: []string{"build.sh", "output_content.txt"},
Paths: []string{buildFile, "output_content.txt"},
},
},
},
Expand All @@ -70,11 +88,11 @@ func (r *Repo) CreateSimpleApp(t *testing.T) *cfg.App {

{
Name: "check",
Command: []string{"sh", "./check.sh"},
Command: checkCommand,
Input: cfg.Input{
Files: []cfg.FileInputs{
{
Paths: []string{"check.sh"},
Paths: []string{checkFile},
},
},
},
Expand All @@ -94,14 +112,14 @@ func (r *Repo) CreateSimpleApp(t *testing.T) *cfg.App {

r.AppCfgs = append(r.AppCfgs, &app)

buildFilePath := filepath.Join(filepath.Join(appDir, "build.sh"))
checkFilePath := filepath.Join(filepath.Join(appDir, "check.sh"))
buildFilePath := filepath.Join(filepath.Join(appDir, buildFile))
checkFilePath := filepath.Join(filepath.Join(appDir, checkFile))

fstest.WriteToFile(t, []byte(`
#!/bin/sh

echo "building app"
cat output_content.txt > output
more output_content.txt > output
`),
buildFilePath)

Expand All @@ -126,12 +144,20 @@ func (r *Repo) CreateAppWithNoOutputs(t *testing.T, appName string) *cfg.App {

inputFileName := fmt.Sprintf("%s.txt", appName)

shell := []string{}
if runtime.GOOS == "windows" {
shell = []string{"cmd", "/C"}
}

buildCommand := append(shell, "echo", "build", appName)
testCommand := append(shell, "echo", "test", appName)

app := cfg.App{
Name: appName,
Tasks: []*cfg.Task{
{
Name: "build",
Command: []string{"echo", "build", appName},
Command: buildCommand,
Input: cfg.Input{
Files: []cfg.FileInputs{
{
Expand All @@ -142,7 +168,7 @@ func (r *Repo) CreateAppWithNoOutputs(t *testing.T, appName string) *cfg.App {
},
{
Name: "test",
Command: []string{"echo", "test", appName},
Command: testCommand,
Input: cfg.Input{
Files: []cfg.FileInputs{
{
Expand Down