Skip to content

Commit

Permalink
Merge pull request #46 from ahoy-cli/dev-bats-tests
Browse files Browse the repository at this point in the history
Add some tests using bats.
  • Loading branch information
Frank Carey committed Nov 22, 2016
2 parents 07a8967 + 6cf6962 commit 83fca54
Show file tree
Hide file tree
Showing 11 changed files with 172 additions and 38 deletions.
6 changes: 6 additions & 0 deletions .ahoy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@ commands:
install:
cmd: "go install"
usage: Build ahoy using go install.
bats:
usage: "Run the bats bash testing command."
cmd: |
bats tests
test:
usage: Run automated tests
cmd: |
ahoy build
FAIL=false
TESTS=(
'go vet'
'go test -v -race '
'golint -set_exit_status'
'bats tests'
)
for i in "${TESTS[@]}"; do
printf "\n=== TEST: $i ===\n\n"
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# Ignore the items build by sphinx for read the docs.
docs/_build
# Ignore the ahoy binary when it's built
ahoy
125 changes: 98 additions & 27 deletions ahoy.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"errors"
"flag"
"fmt"
"github.com/codegangsta/cli"
Expand Down Expand Up @@ -34,73 +35,85 @@ type Command struct {
}

var app *cli.App
var sourcedir string
var sourcefile string
var args []string
var verbose bool
var bashCompletion bool

var version string

//The build version can be set using the go linker flag `-ldflags "-X main.version=$VERSION"`
//Complete command: `go build -ldflags "-X main.version=$VERSION"`
var version string

// AhoyConf stores the global config.
var AhoyConf struct {
srcDir string
srcFile string
}

func logger(errType string, text string) {
errText := ""
if (errType == "error") || (errType == "fatal") || (verbose == true) {
errText = "AHOY! [" + errType + "] ==> " + text + "\n"
log.Print(errText)
// Disable the flags which add date and time for instance.
log.SetFlags(0)
if errType != "debug" {
errText = "[" + errType + "] " + text + "\n"
log.Println(errText)
}

if errType == "fatal" {
panic(errText)
os.Exit(1)
}
}

func getConfigPath(sourcefile string) (string, error) {
var err error
var config = ""

// If a specific source file was set, then try to load it directly.
if sourcefile != "" {
if _, err := os.Stat(sourcefile); err == nil {
return sourcefile, err
}
logger("fatal", "An ahoy config file was specified using -f to be at "+sourcefile+" but couldn't be found. Check your path.")
err = errors.New("An ahoy config file was specified using -f to be at " + sourcefile + " but couldn't be found. Check your path.")
return config, err
}

dir, err := os.Getwd()
if err != nil {
log.Fatal(err)
return config, err
}
for dir != "/" && err == nil {
ymlpath := filepath.Join(dir, ".ahoy.yml")
//log.Println(ymlpath)
if _, err := os.Stat(ymlpath); err == nil {
//log.Println("found: ", ymlpath )
logger("debug", "Found .ahoy.yml at "+ymlpath)
return ymlpath, err
}
// Chop off the last part of the path.
dir = path.Dir(dir)
}
logger("debug", "Can't find a .ahoy.yml file.")
return "", err
}

func getConfig(sourcefile string) (Config, error) {

yamlFile, err := ioutil.ReadFile(sourcefile)
func getConfig(file string) (Config, error) {
var config = Config{}
yamlFile, err := ioutil.ReadFile(file)
if err != nil {
logger("fatal", "An ahoy config file couldn't be found in your path. You can create an example one by using 'ahoy init'.")
err = errors.New("an ahoy config file couldn't be found in your path. You can create an example one by using 'ahoy init'")
return config, err
}

var config Config
// Extract the yaml file into the config varaible.
err = yaml.Unmarshal(yamlFile, &config)
if err != nil {
panic(err)
return config, err
}

// All ahoy files (and imports) must specify the ahoy version.
// This is so we can support backwards compatability in the future.
if config.AhoyAPI != "v2" {
logger("fatal", "Ahoy only supports API version 'v2', but '"+config.AhoyAPI+"' given in "+sourcefile)
err = errors.New("Ahoy only supports API version 'v2', but '" + config.AhoyAPI + "' given in " + sourcefile)
return config, err
}

return config, err
Expand All @@ -117,7 +130,7 @@ func getSubCommands(includes []string) []cli.Command {
continue
}
if include[0] != "/"[0] || include[0] != "~"[0] {
include = filepath.Join(sourcedir, include)
include = filepath.Join(AhoyConf.srcDir, include)
}
if _, err := os.Stat(include); err != nil {
//Skipping files that cannot be loaded allows us to separate
Expand Down Expand Up @@ -188,13 +201,11 @@ func runCommand(name string, c string) {

cReplace := strings.Replace(c, "{{args}}", strings.Join(args, " "), -1)

dir := sourcedir

if verbose {
log.Println("===> AHOY", name, "from", sourcefile, ":", cReplace)
}
cmd := exec.Command("bash", "-c", cReplace)
cmd.Dir = dir
cmd.Dir = AhoyConf.srcDir
cmd.Stdout = os.Stdout
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
Expand Down Expand Up @@ -238,13 +249,15 @@ func addDefaultCommands(commands []cli.Command) []cli.Command {

//TODO Move these to flag.go?
func init() {
logger("debug", "init()")
flag.StringVar(&sourcefile, "f", "", "specify the sourcefile")
flag.BoolVar(&bashCompletion, "generate-bash-completion", false, "")
flag.BoolVar(&verbose, "verbose", false, "")
}

// BashComplete prints the list of subcommands as the default app completion method
func BashComplete(c *cli.Context) {
logger("debug", "BashComplete()")

if sourcefile != "" {
log.Println(sourcefile)
Expand All @@ -257,21 +270,78 @@ func BashComplete(c *cli.Context) {
}
}

func setupApp(args []string) *cli.App {
initFlags(args)
//log.Println(sourcefile)
// NoArgsAction is the application wide default action, for when no flags or arguments
// are passed or when a command doesn't exist.
// Looks like -f flag still works through here though.
func NoArgsAction(c *cli.Context) {
args := c.Args()
if len(args) > 0 {
msg := "Command not found for '" + strings.Join(args, " ") + "'"
logger("fatal", msg)
}

cli.ShowAppHelp(c)

if AhoyConf.srcFile == "" {
logger("error", "No .ahoy.yml found. You can use 'ahoy init' to download an example.")
}

if !c.Bool("help") || !c.Bool("version") {
logger("fatal", "Missing flag or argument.")
}

// Looks like we never reach here.
fmt.Println("ERROR: NoArg Action ")
}

// BeforeCommand runs before every command so arguments or flags must be passed
func BeforeCommand(c *cli.Context) error {
args := c.Args()
if c.Bool("version") {
fmt.Println(version)
return errors.New("don't continue with commands")
}
if c.Bool("help") {
if len(args) > 0 {
cli.ShowCommandHelp(c, args.First())
} else {
cli.ShowAppHelp(c)
}
return errors.New("don't continue with commands")
}
//fmt.Printf("%+v\n", args)
return nil
}

func setupApp(localArgs []string) *cli.App {
var err error
initFlags(localArgs)
// cli stuff
app = cli.NewApp()
app.Action = NoArgsAction
app.Before = BeforeCommand
app.Name = "ahoy"
app.Version = version
app.Usage = "Creates a configurable cli app for running commands."
app.EnableBashCompletion = true
app.BashComplete = BashComplete
overrideFlags(app)

if sourcefile, err := getConfigPath(sourcefile); err == nil {
sourcedir = filepath.Dir(sourcefile)
config, _ := getConfig(sourcefile)
AhoyConf.srcFile, err = getConfigPath(sourcefile)
if err != nil {
logger("fatal", err.Error())
} else {
AhoyConf.srcDir = filepath.Dir(AhoyConf.srcFile)
// If we don't have a sourcefile, then just supply the default commands.
if AhoyConf.srcFile == "" {
app.Commands = addDefaultCommands(app.Commands)
app.Run(os.Args)
os.Exit(0)
}
config, err := getConfig(AhoyConf.srcFile)
if err != nil {
logger("fatal", err.Error())
}
app.Commands = getCommands(config)
app.Commands = addDefaultCommands(app.Commands)
if config.Usage != "" {
Expand Down Expand Up @@ -304,6 +374,7 @@ VERSION:
}

func main() {
logger("debug", "main()")
app = setupApp(os.Args[1:])
app.Run(os.Args)
}
17 changes: 7 additions & 10 deletions ahoy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func TestGetCommands(t *testing.T) {
func TestGetSubCommand(t *testing.T) {
// Since we're not running the app directly, sourcedir doesn't get reset, so
// we need to reset it ourselves. TODO: Remove these globals somehow.
sourcedir = ""
AhoyConf.srcDir = ""

// When empty return empty list of commands.

Expand Down Expand Up @@ -121,7 +121,7 @@ func TestGetSubCommand(t *testing.T) {
})

if len(actual) != 1 {
t.Error("Sourcedir:", sourcedir)
t.Error("Sourcedir:", AhoyConf.srcDir)
t.Error("Failed: expect that two commands with the same name get merged into one.", actual)
}

Expand Down Expand Up @@ -238,14 +238,11 @@ func TestGetConfigPath(t *testing.T) {
// TODO: Passing directory should return default
}

func TestGetConfigPathPanicOnBogusPath(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Error("getConfigPath did not fail when passed a bogus path.")
}
}()

getConfigPath("~/bogus/path")
func TestGetConfigPathErrorOnBogusPath(t *testing.T) {
_, err := getConfigPath("~/bogus/path")
if err == nil {
t.Error("getConfigPath did not fail when passed a bogus path.")
}
}

func appRun(args []string) (string, error) {
Expand Down
1 change: 1 addition & 0 deletions circle.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
test:
pre:
- npm install -g bats
# Make sure this build is using GO > 1.6 for golint to work correctly.
# On CircleCI, only the Trusty 14.04 build has a newer version.
- go get -u github.com/golang/lint/golint
Expand Down
2 changes: 1 addition & 1 deletion flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func initFlags(incomingFlags []string) {

// Reset the sourcedir for when we're testing. Otherwise the global state
// is preserved between the tests.
sourcedir = ""
AhoyConf.srcDir = ""

// Grab the global flags first ourselves so we can customize the yaml file loaded.
// Flags are only parsed once, so we need to do this before cli has the chance to?
Expand Down
4 changes: 4 additions & 0 deletions testdata/simple.ahoy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ahoyapi: v2
commands:
echo:
cmd: echo {{args}}
Empty file added tests/.ahoy.yml
Empty file.
12 changes: 12 additions & 0 deletions tests/flags.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bats

@test "get the version of ahoy with --version" {
run ./ahoy -f testdata/simple.ahoy.yml --version
[ $status -eq 0 ]
[ $(expr "$output" : "[0-9.]\.[0-9.]\.[0-9.]") -ne 0 ]
}

@test "get help instead of running a command with --help" {
result="$(./ahoy -f testdata/simple.ahoy.yml --help echo something)"
[ "$result" != "something" ]
}
26 changes: 26 additions & 0 deletions tests/no-ahoy-file.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bats

setup() {
mv .ahoy.yml tmp.ahoy.yml
}

teardown() {
mv tmp.ahoy.yml .ahoy.yml
}

@test "run ahoy without a command and without a .ahoy.yml file" {
run ./ahoy
[ $status -eq 1 ]
[ "${lines[-2]}" == "[error] No .ahoy.yml found. You can use 'ahoy init' to download an example." ]
[ "${lines[-1]}" == "[fatal] Missing flag or argument." ]
}

@test "run an ahoy command without a .ahoy.yml file" {
run ./ahoy something
[ "$output" == "[fatal] Command not found for 'something'" ]
}

@test "run ahoy init without a .ahoy.yml file" {
run ./ahoy init
[ "${lines[-1]}" == "example.ahoy.yml downloaded to the current directory. You can customize it to suit your needs!" ]
}
15 changes: 15 additions & 0 deletions tests/simple.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bats

@test "display help text and fatal error when no arguments are passed." {
run ./ahoy -f testdata/simple.ahoy.yml
# Should throw an error.
[ $status -ne 0 ]
echo "$output"
[ "${#lines[@]}" -gt 10 ]
[ "${lines[-1]}" == "[fatal] Missing flag or argument." ]
}

@test "run a simple ahoy command: echo" {
result="$(./ahoy -f testdata/simple.ahoy.yml echo something)"
[ "$result" == "something" ]
}

0 comments on commit 83fca54

Please sign in to comment.