Skip to content

Commit

Permalink
Implement process wrapper & add executor tests (#27)
Browse files Browse the repository at this point in the history
* Setup executor tests & update parser tests

* Add process implementation and mock

* Add executor tests to cover watching

* Add short timeout for ci

* Refactor Parser (#28)

* Parser interface, dependencies udpated

* Get options moved to internal

* Internal/util.go refactor and tests

Co-authored-by: Dugi <[email protected]>

* Remove useless tests

Co-authored-by: Sam <[email protected]>
  • Loading branch information
dugajean and samthom authored Oct 23, 2022
1 parent 8f4df12 commit d59a3be
Show file tree
Hide file tree
Showing 16 changed files with 511 additions and 240 deletions.
1 change: 1 addition & 0 deletions .github/workflows/push_pull.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ on:

jobs:
ci:
timeout-minutes: 2
strategy:
fail-fast: false
matrix:
Expand Down
11 changes: 8 additions & 3 deletions cmd/cli/main.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package main

import (
"context"
"fmt"
"os"

app "github.com/dugajean/goke/internal"
"github.com/dugajean/goke/internal/cli"
)

func main() {
argIndex := app.PermutateArgs(os.Args)
opts := cli.GetOptions()
opts := app.GetOptions()

handleGlobalFlags(&opts)

Expand All @@ -20,13 +20,18 @@ func main() {
os.Exit(1)
}

// Wrappers
fs := app.LocalFileSystem{}
proc := app.ShellProcess{}

// Main components
p := app.NewParser(cfg, &opts, &fs)
p.Bootstrap()

l := app.NewLockfile(p.GetFilePaths(), &opts, &fs)
l.Bootstrap()

e := app.NewExecutor(&p, &l, &opts)
ctx := context.Background()
e := app.NewExecutor(&p, &l, &opts, &proc, &fs, &ctx)
e.Start(parseTaskName(argIndex))
}
4 changes: 3 additions & 1 deletion goke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ main:
- "go build -o './build/${BINARY}' ./cmd/cli"

genmocks:
files: [internal/filesystem.go]
files: [internal/filesystem.go, internal/process.go]
run:
- "mockery --name=FileSystem --recursive --output=internal/tests --outpkg=tests --filename=filesystem_mock.go"
- "mockery --name=Process --recursive --output=internal/tests --outpkg=tests --filename=process_mock.go"

greet-cats:
files: ["foo"]
run:
- 'echo "Hello Frey"'
- 'echo "Hello Bunny"'
Expand Down
21 changes: 0 additions & 21 deletions internal/cli/options.go

This file was deleted.

132 changes: 132 additions & 0 deletions internal/cli/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package cli

import (
"fmt"
"os"
"os/exec"
"regexp"
"strings"
)

var osCommandRegexp = regexp.MustCompile(`\$\((.+)\)`)
var osEnvRegexp = regexp.MustCompile(`\$\{(.+)\}`)

// Parses the interpolated system commands, ie. "Hello $(echo 'World')" and returns it.
// Returns the command wrapper in $() and without the wrapper.
func parseSystemCmd(re *regexp.Regexp, str string) (string, string) {
match := re.FindAllStringSubmatch(str, -1)

if len(match) > 0 && len(match[0]) > 0 {
return match[0][0], match[0][1]
}

return "", ""
}

// prase system commands and store results to env
func SetEnvVariables(vars map[string]string) (map[string]string, error) {
retVars := make(map[string]string)
for k, v := range vars {
_, cmd := parseSystemCmd(osCommandRegexp, v)

if cmd == "" {
retVars[k] = v
_ = os.Setenv(k, v)
continue
}

splitCmd, err := ParseCommandLine(os.ExpandEnv(cmd))
if err != nil {
return retVars, err
}

out, err := exec.Command(splitCmd[0], splitCmd[1:]...).Output()
if err != nil {
return retVars, err
}

outStr := strings.TrimSpace(string(out))
retVars[k] = outStr
_ = os.Setenv(k, outStr)
}

return retVars, nil
}

// Parses the command string into an array of [command, args, args]...
func ParseCommandLine(command string) ([]string, error) {
var args []string
state := "start"
current := ""
quote := "\""
escapeNext := true

for i := 0; i < len(command); i++ {
c := command[i]

if state == "quotes" {
if string(c) != quote {
current += string(c)
} else {
args = append(args, current)
current = ""
state = "start"
}
continue
}

if escapeNext {
current += string(c)
escapeNext = false
continue
}

if c == '\\' {
escapeNext = true
continue
}

if c == '"' || c == '\'' {
state = "quotes"
quote = string(c)
continue
}

if state == "arg" {
if c == ' ' || c == '\t' {
args = append(args, current)
current = ""
state = "start"
} else {
current += string(c)
}
continue
}

if c != ' ' && c != '\t' {
state = "arg"
current += string(c)
}
}

if state == "quotes" {
return []string{}, fmt.Errorf("unclosed quote in command: %s", command)
}

if current != "" {
args = append(args, current)
}

return args, nil
}

// Replace the placeholders with actual environment variable values in string pointer.
// Given that a string pointer must be provided, the replacement happens in place.
func ReplaceEnvironmentVariables(re *regexp.Regexp, str *string) {
resolved := *str
raw, env := parseSystemCmd(re, resolved)

if raw != "" && env != "" {
*str = strings.Replace(resolved, raw, os.Getenv(env), -1)
}
}
71 changes: 71 additions & 0 deletions internal/cli/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package cli

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
)

func TestParseSystemCmd(t *testing.T) {

cmds := []string{
"$(echo 'Hello Thor')",
"hello world",
}

want := [][]string{
{"$(echo 'Hello Thor')", "echo 'Hello Thor'"},
{"", ""},
}

for i, cmd := range cmds {
got0, got1 := parseSystemCmd(osCommandRegexp, cmd)
assert.Equal(t, want[i][0], got0, "expected "+want[i][0]+", got ", got0)
assert.Equal(t, want[i][1], got1, "expected "+want[i][1]+", got ", got1)
}

}

func TestSetEnvVariables(t *testing.T) {

values := map[string]string{
"THOR": "Lord of thunder",
"THOR_CMD": "$(echo 'Hello Thor')",
}

want := map[string]string{
"THOR": "Lord of thunder",
"THOR_CMD": "Hello Thor",
}

got, _ := SetEnvVariables(values)
assert.Equal(t, want["THOR"], os.Getenv("THOR"))
assert.Equal(t, want["THOR_CMD"], os.Getenv("THOR_CMD"))

for k := range got {
assert.Equal(t, want[k], got[k])
}
}

func TestParseCommandLine(t *testing.T) {
t.Skip()
}

func TestReplaceEnvironmentVariables(t *testing.T) {
values := map[string]string{
"THOR": "Lord of thunder",
"LOKI": "Lord of deception",
}

for k, v := range values {
t.Setenv(k, v)
}

str := "I am ${THOR}"
want := "I am Lord of thunder"

ReplaceEnvironmentVariables(osEnvRegexp, &str)

assert.Equal(t, want, str, "wrong env value is injected")
}
Loading

0 comments on commit d59a3be

Please sign in to comment.