Cobra + Viper Golang How to test subcommands? - go

I am developing an web app with Go. So far so good, but now I am integrating Wercker as a CI tool and started caring about testing. But my app relies heavily on Cobra/Viper configuration/flags/environment_variables scheme, and I do not know how to properly init Viper values before running my test suite. Any help would be much appreciated.

When I use Cobra/Viper or any other combination of CLI helpers, my way of doing this is to have the CLI tool run a function whose sole purpose will be to get arguments and pass them to another method who will do the actual work.
Here is a short (and dumb) example using Cobra :
package main
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
func main() {
var Cmd = &cobra.Command{
Use: "boom",
Short: "Explode all the things!",
Run: Boom,
}
if err := Cmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(-1)
}
}
func Boom(cmd *cobra.Command, args []string) {
boom(args...)
}
func boom(args ...string) {
for _, arg := range args {
println("boom " + arg)
}
}
Here, the Boom function is hard to test, but the boom one is easy.
You can see another (non-dumb) example of this here (and the correspond test here).

i have found an easy way to test commands with multiple level sub commands, it is not professional but it worked well.
assume we have a command like this
RootCmd = &cobra.Command{
Use: "cliName",
Short: "Desc",
}
SubCmd = &cobra.Command{
Use: "subName",
Short: "Desc",
}
subOfSubCmd = &cobra.Command{
Use: "subOfSub",
Short: "Desc",
Run: Exec
}
//commands relationship
RootCmd.AddCommand(SubCmd)
SubCmd.AddCommand(subOfSubCmd)
When testing the subOfSubCmd we can do this way:
func TestCmd(t *testing.T) {
convey.Convey("test cmd", t, func() {
args := []string{"subName", "subOfSub"}
RootCmd.SetArgs(args)
RootCmd.Execute()
})
}

Related

Handle Logrus and Cobra CLI in Golang

I am relatively new to the packages Cobra and Logrus in Golang and there is one little thing I would to ask for help when it comes to integrating them. I am working on a new Golang Cobra CLI which contains several commands/sub-commands. I'd like to use Logrus for logging inside my subcommands but I cannot find a good way to add new fields to the logger and pass them all to my subcommands.
This is what I have so far (I simplified my code for better explanation of my purpose):
├──pkg/
├──logger/
| ├──logger.go
├──cmd/
├── root.go
├──scmd/
| ├──scmd.go
// root.go
package cmd
func ExecuteRoot() {
rootCmd := getRootCmd()
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}
func getRootCmd() *cobra.Command {
rootCmd := &cobra.Command{
Use: "mycli",
PersistentPreRun: enableLogs,
}
rootCmd.AddCommand(scmd.Getscmd())
return rootCmd
}
func enableLogs(cmd *cobra.Command, args []string) {
logger.ConfigureLogger(cmd.Flag("log-level").Value.String())
}
// logger.go
package logger
import (
"github.com/sirupsen/logrus"
)
var MyCli *logrus.Entry
func ConfigureLogger(d string) {
lv, err := logrus.ParseLevel(d)
if err != nil {
logrus.Fatal(err)
}
logrus.SetLevel(lv)
logrus.SetFormatter(&logrus.TextFormatter{
DisableQuote: true,
ForceColors: true,
})
MyCli = logrus.WithFields(logrus.Fields{"foo1": "bar1", "foo2": "bar2"})
}
// scmd.go
package scmd
import (
"pkg/logger"
"github.com/spf13/cobra"
)
func Getscmd() *cobra.Command {
serviceUpdateCmd := &cobra.Command{
Use: "scmd",
Run: runServiceUpdateCmd,
}
return serviceUpdateCmd
}
func runServiceUpdateCmd(cmd *cobra.Command, args []string) {
logger.MyCli.Info("start scmd")
// here what the command does ...
}
If I run it, I get what I am expecting: My subcommands (in this case scmd) logs at the level I set my flag log-level and the fields defined in my package logger are passed ("foo1": "bar1", "foo2": "bar2"). However, I feel it is not the right way and could become problematic when creating unit test as I am using the global variable var MyCli from my logger package. Moreover, I should import it and use it as logger.MyCli in every Info, error, warn, etc line I want to log.
My question is whether there is a better approach to pass fields in Logrus on all subcommands I create or the way I explained above is my only option.
Any help or idea to improve my code is really welcome.
Thanks!

Testing urfave/cli based applications with go

I'm writing a small CLI application in Golang using urfave/cli framework and I'd like to write tests for it, but I can't find any useful information on how to test CLI applications, specifically written with the urfave/cli library. I have a lot of flags in the application and some of them are mutually exclusive and I'd like a proper test to stay on top of them - does anyone have an idea how to do it the right way?
EDIT:
Consider the following minimal example of application with several flags and restrictions around them. How would you test these flags usage (requirements, exclusivity, etc.) and how they influence the functions when they're set or not?
package main
import (
"errors"
"fmt"
"os"
"github.com/urfave/cli"
)
func doSomething(flag1 string, flag2 string, flag3 bool, flag4 bool) error {
err := errors.New("something")
return err
}
func main() {
app := cli.NewApp()
app.Name = "greet"
app.Usage = "fight the loneliness!"
var flag1, flag2 string
var flag3, flag4 bool
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "flag1",
Value: "",
Usage: "flag1",
Destination: &flag1,
},
cli.StringFlag{
Name: "flag2",
Value: "",
Usage: "flag2",
Destination: &flag2,
},
cli.BoolFlag{
Name: "flag3",
Usage: "flag3",
Destination: &flag3,
},
cli.BoolFlag{
Name: "flag4",
Usage: "flag4",
Destination: &flag4,
},
}
app.Action = func(c *cli.Context) error {
if flag1 != "" && c.NumFlags() > 1 {
fmt.Println("--flag1 flag cannot be used with any other flags")
cli.ShowAppHelp(c)
os.Exit(1)
}
if flag1 == "" && flag2 == "" || c.NumFlags() < 1 {
fmt.Println("--flag2 is required")
cli.ShowAppHelp(c)
os.Exit(1)
}
if flag3 && flag4 {
fmt.Println("--flag3 and --flag4 flags are mutually exclusive")
cli.ShowAppHelp(c)
os.Exit(1)
}
err := doSomething(flag1, flag2, flag3, flag4)
return err
}
}
As Adrian correctly wrote
the same way you test anything else
Given a slightly modified example of the sample code of the project
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli"
)
func Friend(c *cli.Context) error {
fmt.Println("Hello friend!")
return nil
}
func main() {
app := cli.NewApp()
app.Name = "greet"
app.Usage = "fight the loneliness!"
app.Action = Friend
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
Since this code actually prints something instead of returning a value you can evaluate, you could use a testable example
func ExampleFriend(){
// Yeah, technically, we can save the error check with the code above
// but this illustrates how you can make sure the output
// is not what the testable example expects.
if err := Friend(nil){
fmt.Printf("Friend: %s",err)
}
// Output:
// Hello friend!
}
Note that Action expects an ActionFunc. Where you define that ActionFunc is pretty much your thing. It could even come from a different package. So it is your design on how good your application will be testable.
Edit The signature of the value Action expects will change in the future, at least according to the docs. I already find it questionable to use interface{} to be able to pass nil to Action, and then check and type assert for ActionFunc, where a no-op ActionFunc would actually serve the same purpose, but removing an error return value really makes me scratch my head. I strongly recommend to have a look at alecthomas/kingpin for smaller to medium size applications or spf13/cobra, which is suitable even for the most complex of cli applications.

Why is Cobra not reading my configuration file?

The documentation in Cobra and Viper are confusing me. I did cobra init fooproject and then inside the project dir I did cobra add bar. I have a PersistentFlag that is named foo and here is the init function from the root command.
func Execute() {
if err := RootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(-1)
}
fmt.Println(cfgFile)
fmt.Println("fooString is: ", fooString)
}
func init() {
cobra.OnInitialize(initConfig)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags, which, if defined here,
// will be global for your application.
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.fooproject.yaml)")
RootCmd.PersistentFlags().StringVar(&fooString, "foo", "", "loaded from config")
viper.BindPFlag("foo", RootCmd.PersistentFlags().Lookup("foo"))
// Cobra also supports local flags, which will only run
// when this action is called directly.
RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
My configuration file looks like this...
---
foo: aFooString
And when I call go run main.go I see this...
A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.
Usage:
fooproject [command]
Available Commands:
bar A brief description of your command
help Help about any command
Flags:
--config string config file (default is $HOME/.fooproject.yaml)
--foo string loaded from config
-h, --help help for fooproject
-t, --toggle Help message for toggle
Use "fooproject [command] --help" for more information about a command.
fooString is:
When I call go run main.go bar I see this...
Using config file: my/gopath/github.com/user/fooproject/.fooproject.yaml
bar called
fooString is:
So it is using the configuration file, but neither one of them seems to be reading it. Maybe I am misunderstanding the way that Cobra and Viper work. Any ideas?
To combine spf13/cobra and spf13/viper, first define the flag with Cobra:
RootCmd.PersistentFlags().StringP("foo", "", "loaded from config")
Bind it with Viper:
viper.BindPFlag("foo", RootCmd.PersistentFlags().Lookup("foo"))
And get the variable via the Viper's method:
fmt.Println("fooString is: ", viper.GetString("foo"))
Since Viper values are somewhat inferior to pflags (e.g. no support for custom data types), I was not satisfied with answer "use Viper to retrieve values", and ended up writing small helper type to put values back in pflags.
type viperPFlagBinding struct {
configName string
flagValue pflag.Value
}
type viperPFlagHelper struct {
bindings []viperPFlagBinding
}
func (vch *viperPFlagHelper) BindPFlag(configName string, flag *pflag.Flag) (err error) {
err = viper.BindPFlag(configName, flag)
if err == nil {
vch.bindings = append(vch.bindings, viperPFlagBinding{configName, flag.Value})
}
return
}
func (vch *viperPFlagHelper) setPFlagsFromViper() {
for _, v := range vch.bindings {
v.flagValue.Set(viper.GetString(v.configName))
}
}
func main() {
var rootCmd = &cobra.Command{}
var viperPFlagHelper viperPFlagHelper
rootCmd.PersistentFlags().StringVar(&config.Password, "password", "", "API server password (remote HTTPS mode only)")
viperPFlagHelper.BindPFlag("password", rootCmd.Flag("password"))
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
err := viper.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
return err
}
}
viperPFlagHelper.setPFlagsFromViper()
return nil
}
}
For those facing the same issue, the problem is on this call:
cobra.OnInitialize(initConfig)
The function initConfig is not executed directly but append to an array of initializers https://github.com/spf13/cobra/blob/master/cobra.go#L80
So, the values stored in your config file will be loaded when the Run field is executed:
rootCmd = &cobra.Command{
Use: "example",
Short: "example cmd",
Run: func(cmd *cobra.Command, args []string) { // OnInitialize is called first
fmt.Println(viper.AllKeys())
},
}
#WGH 's answer is correct, you can do something with the persistent flag in rootCmd.PersistenPreRun, which will be available in all other sub commands.
var rootCmd = &cobra.Command{
PersistentPreRun: func(_ *cobra.Command, _ []string) {
if persistentflag {
// do something with persistentflag
}
},

Golang: Testing with init() func

Hi I am new to Go and I am writing a simple app which gets some configuration from the env variables. I do this in the init function as shown below.
type envVars struct {
Host string `env:"APP_HOST"`
Username string `env:"APP_USERNAME"`
Password string `env:"APP_PASSWORD"`
}
var envConfig envVars
func init() {
if err := env.Parse(&envConfig); err != nil {
log.Fatal(err)
}
}
I wrote test to verify of the env variables are being read correctly. But the problem is that my program's init func gets called even before my test's init func. Is there any way I can do some sort of setup before my program's init func gets called.
func init() {
os.Setenv("APP_HOST", "http://localhost:9999")
os.Setenv("APP_USERNAME", "john")
os.Setenv("APP_PASSWORD", "doe")
}
func TestEnvConfig(t *testing.T) {
assert.NotNil(t, envConfig)
assert.Equal(t, "http://localhost:9999", envConfig.Host)
}
You can use the TestMain func to control what happens before and after your tests.
For example:
func TestMain(m *testing.M) {
// Write code here to run before tests
// Run tests
exitVal := m.Run()
// Write code here to run after tests
// Exit with exit value from tests
os.Exit(exitVal)
}
func TestYourFunc(t *testing.T) {
// Test code
}
You can add a Test_parse_params(t *testing.T) function before your real tests. Look like this:
type envVars struct {
Host string `env:"APP_HOST"`
Username string `env:"APP_USERNAME"`
Password string `env:"APP_PASSWORD"`
}
var envConfig envVars
//parse command params
func Test_parse_params(t *testing.T) {
if err := env.Parse(&envConfig); err != nil {
log.Fatal(err)
}
}
func Test_real_test(t *testing.T) {
....
}
No, you shouldn't expect init() run in some order, (in fact it based on file loaded order, but still, you should not count on it).
The simple way is, if you want to test it, use a shell script to run you test, or something like Makefile.
Shell example:
set +e
export APP_HOST=http://localhost:9999
export APP_USERNAME=john
export APP_PASSWORD=doe
go test .
unset APP_HOST
unset APP_USERNAME
unset APP_PASSWORD
or a single line command:
APP_HOST=http://localhost:9999 APP_USERNAME=john APP_PASSWORD=doe go test .
Edit:
Other solution: move out the read env from init func.
func init(){
envInit()
}
func envInit(){
if err := env.Parse(&envConfig); err != nil {
log.Fatal(err)
}
}
Then you can call again envInit in your test to make sure it works.
Less than ideal, but this works for me.
Inside of the package that you're testing:
func init() {
if len(os.Args) > 1 && os.Args[1][:5] == "-test" {
log.Println("testing")//special test setup goes goes here
return // ...or just skip the setup entirely
}
//...
}

How to speed up Google App Engine Go unit tests?

I am currently writing a lot of unit tests for my package that runs on GAE Go. The package in question is focused on data saving and loading to and from appengine/datastore. As such, I have about 20 unit test files that look a bit like this:
package Data
import (
"appengine"
"appengine/aetest"
. "gopkg.in/check.v1"
"testing"
)
func TestUsers(t *testing.T) { TestingT(t) }
type UsersSuite struct{}
var _ = Suite(&UsersSuite{})
const UserID string = "UserID"
func (s *UsersSuite) TestSaveLoad(cc *C) {
c, err := aetest.NewContext(nil)
cc.Assert(err, IsNil)
defer c.Close()
...
As a result, each individual test file appears to be starting its own version of devappserver:
Repeat this 20 times and my unit tests run for over 10 minutes.
I am wondering, how can I speed up the execution of my testing suite? Should I have just one file that creates aetest.NewContext and passes that onwards, or is it due to me using separate Suites for each unit test? How can I speed this thing up?
You can use a custom TestMain function:
var ctx aetest.Context
var c aetest.Context
func TestMain(m *testing.M) {
var err error
ctx, err = aetest.NewContext(nil)
if err != nil {
panic(err)
}
code := m.Run() // this runs the tests
ctx.Close()
os.Exit(code)
}
func TestUsers(t *testing.T) {
// use ctx here
}
This way the dev server is started once for all the tests. More details on TestMain are available here: http://golang.org/pkg/testing/#hdr-Main.

Resources