Skip to content
New issue

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

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

Already on GitHub? # to your account

Added Viper configuration (#31) #75

Merged
merged 16 commits into from
Jul 29, 2022
Merged
Show file tree
Hide file tree
Changes from 4 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
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,68 @@ Gremlins will report each mutation as:
explicitly.
- `NOT VIABLE`: The mutation makes the build fail.


### Configuration

Gremlins uses [viper](https://github.com/spf13/viper) for the configuration.
In particular, the options can be passed in the following ways

- specific command flags
- environment variables
- configuration file

in which each item takes precedence over the following in the list.

<u>1. Specific command flags</u>

Example:
```
$ gremlins unleash -h
Unleashes the gremlins and performs mutation testing on a Go module.

Usage:
gremlins unleash [path of the Go module] [flags]

Aliases:
unleash, run, r

Flags:
-d, --dry-run find mutations but do not executes tests
-h, --help help for unleash
-t, --tags string a comma-separated list of build tags
```
<u>2. Environment Variables</u>

The environment variables must be set with the following syntax:

```
GREMLINS_<COMMAND NAME>_<FLAG NAME>
```

In which every dash in the option name must be replaced with an underscore

Example:

```
$ GREMLINS_UNLEASH_DRY_RUN=true gremlins unleash
```

<u>3. Configuration File</u>

The configuration must be named `.gremlins.yaml` and must be in the following format:

```yaml
unleash:
dry-run: false
tags: ...
```

and can be placed in one of the following folder (in order)

- the current folder
- `/etc/gremlins`
- `$HOME/.gremlins`

### Supported mutations

#### Conditionals Boundaries
Expand Down
52 changes: 49 additions & 3 deletions cmd/gremlins.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,39 @@
package cmd

import (
"strings"

"github.com/spf13/cobra"
"github.com/spf13/viper"
)

const (
gremlinsCfgName = ".gremlins"
gremlinsEnvVarPrefix = "GREMLINS"
)

// Execute initialises a new Cobra root command (gremlins) with a custom version
// string used in the `-v` flag results.
func Execute(version string) error {
return newRootCmd(version).execute()
rootCmd, err := newRootCmd(version)
if err != nil {
return err
}

return rootCmd.execute()
}

type gremlinsCmd struct {
cmd *cobra.Command
}

func (gc gremlinsCmd) execute() error {

return gc.cmd.Execute()
}

func newRootCmd(version string) *gremlinsCmd {
func newRootCmd(version string) (*gremlinsCmd, error) {

cmd := &cobra.Command{
Use: "gremlins",
Short: `Gremlins is a mutation testing tool for Go projects, made with love by go-gremlins
Expand All @@ -43,9 +58,40 @@ and friends.
Version: version,
}

cmd.AddCommand(newUnleashCmd().cmd)
configPaths := []string{
".",
"/etc/gremlins",
"$HOME/.gremlins",
}
unleashCmd, err := newUnleashCmd(getViper(configPaths))
if err != nil {
return nil, err

}
cmd.AddCommand(unleashCmd.cmd)

return &gremlinsCmd{
cmd: cmd,
}, nil
}

func getViper(configPaths []string) *viper.Viper {
// setting viper
v := viper.New()
v.SetConfigName(gremlinsCfgName)
v.SetConfigType("yaml")

for _, p := range configPaths {
v.AddConfigPath(p)
}

v.SetEnvPrefix(gremlinsEnvVarPrefix)
replacer := strings.NewReplacer(".", "_", "-", "_")
v.SetEnvKeyReplacer(replacer)
v.AutomaticEnv()

_ = v.ReadInConfig() // ignoring error if file not present

return v

}
87 changes: 87 additions & 0 deletions cmd/gremlins_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package cmd

import (
"os"
"testing"

"github.com/google/go-cmp/cmp"
)

type envEntry struct {
name string
value string
}

func TestConfiguration(t *testing.T) {

testCases := []struct {
name string
configPaths []string
envEntries []envEntry
wantedConfig map[string]interface{}
}{
{
name: "from cfg",
configPaths: []string{"./testdata/config1"},
wantedConfig: map[string]interface{}{
"unleash.dry-run": true,
"unleash.tags": "tag1,tag2,tag3",
},
},
{
name: "from cfg multi",
configPaths: []string{"./testdata/config2", "./testdata/config1"},
wantedConfig: map[string]interface{}{
"unleash.dry-run": true,
"unleash.tags": "tag1.2,tag2.2,tag3.2",
},
},
{
name: "from env",
envEntries: []envEntry{
{name: "GREMLINS_UNLEASH_DRY_RUN", value: "true"},
{name: "GREMLINS_UNLEASH_TAGS", value: "tag1,tag2,tag3"},
},
wantedConfig: map[string]interface{}{
"unleash.dry-run": "true",
"unleash.tags": "tag1,tag2,tag3",
},
},
{
name: "from cfg override with env",
envEntries: []envEntry{
{name: "GREMLINS_UNLEASH_TAGS", value: "tag1env,tag2env,tag3env"},
},
configPaths: []string{"./testdata/config1"},
wantedConfig: map[string]interface{}{
"unleash.dry-run": true,
"unleash.tags": "tag1env,tag2env,tag3env",
},
},
}

for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
if tc.envEntries != nil {
for _, e := range tc.envEntries {
os.Setenv(e.name, e.value)
}
}
v := getViper(tc.configPaths)

for key, wanted := range tc.wantedConfig {
got := v.Get(key)
if got != wanted {
t.Errorf(cmp.Diff(got, wanted))
}
}

if tc.envEntries != nil {
for _, e := range tc.envEntries {
os.Unsetenv(e.name)
}
}
})
}
}
3 changes: 3 additions & 0 deletions cmd/testdata/config1/.gremlins.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
unleash:
dry-run: true
tags: tag1,tag2,tag3
3 changes: 3 additions & 0 deletions cmd/testdata/config2/.gremlins.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
unleash:
dry-run: true
tags: tag1.2,tag2.2,tag3.2
30 changes: 23 additions & 7 deletions cmd/unleash.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,25 @@ import (
"github.com/go-gremlins/gremlins/pkg/mutator/workdir"
"github.com/go-gremlins/gremlins/pkg/report"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

type unleashCmd struct {
cmd *cobra.Command
}

func newUnleashCmd() *unleashCmd {
var dryRun bool
var buildTags string
const (
commandName = "unleash"
paramDryRun = "dry-run"
paramBuildTags = "tags"
)

func newUnleashCmd(v *viper.Viper) (*unleashCmd, error) {
dryRun := v.GetBool(fmt.Sprintf("%s.%s", commandName, paramDryRun))
buildTags := v.GetString(fmt.Sprintf("%s.%s", commandName, paramBuildTags))

cmd := &cobra.Command{
Use: "unleash [path of the Go module]",
Use: fmt.Sprintf("%s [path of the Go module]", commandName),
Aliases: []string{"run", "r"},
Args: cobra.MaximumNArgs(1),
Short: "Executes the mutation testing process",
Expand Down Expand Up @@ -90,10 +97,19 @@ func newUnleashCmd() *unleashCmd {
},
}

cmd.Flags().BoolVarP(&dryRun, "dry-run", "d", false, "find mutations but do not executes tests")
cmd.Flags().StringVarP(&buildTags, "tags", "t", "", "a comma-separated list of build tags")
cmd.Flags().BoolVarP(&dryRun, paramDryRun, "d", false, "find mutations but do not executes tests")
err := viper.BindPFlag(fmt.Sprintf("%s.%s", commandName, paramDryRun), cmd.Flags().Lookup(paramDryRun))
if err != nil {
return nil, err
}

cmd.Flags().StringVarP(&buildTags, paramBuildTags, "t", "", "a comma-separated list of build tags")
err = viper.BindPFlag(fmt.Sprintf("%s.%s", commandName, paramBuildTags), cmd.Flags().Lookup(paramBuildTags))
if err != nil {
return nil, err
}

return &unleashCmd{
cmd: cmd,
}
}, nil
}
81 changes: 81 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright 2022 The Gremlins Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/*
Gremlins is a mutation testing tool for Go.
It has been made to work well on smallish Go modules, for example microservices, on which it helps validate the tests, aids the TDD process and can be used as a CI quality gate.
As of now, Gremlins doesn't work very well on very big Go modules, mainly because a run can take hours to complete.

Usage

To execute a mutation test run, from the root of a Go module execute:

$ gremlins unleash

If the Go test run needs build tags, they can be passed along:

$ gremlins unleash --tags "tag1,tag2"

To perform the analysis without actually running the tests:

$ gremlins unleash --dry-run


Gremlins will report each mutation as:
- RUNNABLE: In dry-run mode, a mutation that can be tested.
- NOT COVERED: A mutation not covered by tests; it will not be tested.
- KILLED: The mutation has been caught by the test suite.
- LIVED: The mutation hasn't been caught by the test suite.
- TIMED OUT: The tests timed out while testing the mutation: the mutation actually made the tests fail, but not explicitly.
- NOT VIABLE: The mutation makes the build fail.

Configuration

Gremlins uses Viper (https://github.com/spf13/viper) for the configuration.

In particular, the options can be passed in the following ways

- specific command flags
- environment variables
- configuration file

in which each item takes precedence over the following in the list.
The environment variables must be set with the following syntax:

GREMLINS_<COMMAND NAME>_<FLAG NAME>

in which every dash in the option name must be replaced with an underscore.

Example:

$ GREMLINS_UNLEASH_DRY_RUN=true gremlins unleash


The configuration must be named
.gremlins.yaml
and must be in the following format:

unleash:
dry-run: false
tags: ...

and can be placed in one of the following folder (in order)

- the current folder
- /etc/gremlins
- $HOME/.gremlins
*/
package gremlins
Loading