package main import ( "fmt" "io/ioutil" "os" "path/filepath" "reflect" "regexp" "runtime" "testing" ) var ( rpcuserRegexp = regexp.MustCompile("(?m)^rpcuser=.+$") rpcpassRegexp = regexp.MustCompile("(?m)^rpcpass=.+$") ) // Define a struct "configCmdLineOnly" containing a subset of configuration // parameters which are command-line only. These fields are copied line-by-line // from "config" struct in "config.go", and the field names, types, and tags must // match for the test to work. // type configCmdLineOnly struct { ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"` DbType string `long:"dbtype" description:"Database backend to use for the Block Chain"` DropCfIndex bool `long:"dropcfindex" description:"Deletes the index used for committed filtering (CF) support from the database on start up and then exits."` DropTxIndex bool `long:"droptxindex" description:"Deletes the hash-based transaction index from the database on start up and then exits."` DisableCheckpoints bool `long:"nocheckpoints" description:"Disable built-in checkpoints. Don't do this unless you know what you're doing."` NoWinService bool `long:"nowinservice" description:"Do not start as a background service on Windows -- NOTE: This flag only works on the command line, not in the config file"` DisableStallHandler bool `long:"nostalldetect" description:"Disables the stall handler system for each peer, useful in simnet/regtest integration tests frameworks"` RegressionTest bool `long:"regtest" description:"Use the regression test network"` SimNet bool `long:"simnet" description:"Use the simulation test network"` SigNet bool `long:"signet" description:"Use the signet test network"` SigNetChallenge string `long:"signetchallenge" description:"Connect to a custom signet network defined by this challenge instead of using the global default signet test network -- Can be specified multiple times"` SigNetSeedNode []string `long:"signetseednode" description:"Specify a seed node for the signet network instead of using the global default signet network seed nodes"` ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"` } func fieldEq(f1, f2 reflect.StructField) bool { return (f1.Name == f2.Name) && (f1.Type == f2.Type) && (f1.Tag == f2.Tag) } func TestSampleConfigFileComplete(t *testing.T) { // find out where the sample config lives _, path, _, ok := runtime.Caller(0) if !ok { t.Fatalf("Failed finding config file path") } sampleConfigFile := filepath.Join(filepath.Dir(path), sampleConfigFilename) // Read the sample config file content, err := ioutil.ReadFile(sampleConfigFile) if err != nil { t.Fatalf("Failed reading sample config file: %v", err) } allFields := reflect.VisibleFields(reflect.TypeOf(config{})) cmdlineFields := reflect.VisibleFields(reflect.TypeOf(configCmdLineOnly{})) // Verify cmdlineFields is a subset of allFields. for _, cf := range cmdlineFields { // Check for presence of field "cf" in config struct. var field *reflect.StructField for _, f := range allFields { f := f // new instance of loop var for return if fieldEq(cf, f) { field = &f break } } if field == nil { t.Errorf("cmdline field: %s type: %s is not present in type %s", cf.Name, cf.Type, reflect.TypeOf(config{})) } } // Verify sample config covers all parameters. for _, f := range allFields { longname, ok := f.Tag.Lookup("long") if !ok { // Field has no long-form name, so not eligible for // inclusion in sample config. continue } // Check for presence of field "f" in our configCmdLineOnly struct. var cmdline *reflect.StructField for _, cf := range cmdlineFields { cf := cf // new instance of loop var for return if fieldEq(cf, f) { cmdline = &cf break } } // Look for assignment (="), or commented assignment ("; ="). pattern := fmt.Sprintf("(?m)^(;\\s*)?%s=.*$", longname) assignment, err := regexp.Compile(pattern) if err != nil { t.Errorf("config field: %s longname: %s failed compiling regexp (%s): %v", f.Name, longname, pattern, err) continue } assigned := assignment.Match(content) // Field "f" must be present in either the sample config (=X), // or it must be one of the command line only fields, but not both. if !assigned && (cmdline == nil) { t.Errorf("config field: %s longname: %s assignment (%s) should be present in %s", f.Name, longname, assignment, sampleConfigFilename) } if assigned && (cmdline != nil) { t.Errorf("config field: %s longname: %s should not be present in both %s and type %s", f.Name, longname, sampleConfigFilename, reflect.TypeOf(configCmdLineOnly{}).Name()) } } } func TestCreateDefaultConfigFile(t *testing.T) { // find out where the sample config lives _, path, _, ok := runtime.Caller(0) if !ok { t.Fatalf("Failed finding config file path") } sampleConfigFile := filepath.Join(filepath.Dir(path), "sample-lbcd.conf") // Setup a temporary directory tmpDir, err := ioutil.TempDir("", "lbcd") if err != nil { t.Fatalf("Failed creating a temporary directory: %v", err) } testpath := filepath.Join(tmpDir, "test.conf") // copy config file to location of lbcd binary data, err := ioutil.ReadFile(sampleConfigFile) if err != nil { t.Fatalf("Failed reading sample config file: %v", err) } appPath, err := filepath.Abs(filepath.Dir(os.Args[0])) if err != nil { t.Fatalf("Failed obtaining app path: %v", err) } tmpConfigFile := filepath.Join(appPath, "sample-lbcd.conf") err = ioutil.WriteFile(tmpConfigFile, data, 0644) if err != nil { t.Fatalf("Failed copying sample config file: %v", err) } // Clean-up defer func() { os.Remove(testpath) os.Remove(tmpConfigFile) os.Remove(tmpDir) }() err = createDefaultConfigFile(testpath) if err != nil { t.Fatalf("Failed to create a default config file: %v", err) } content, err := ioutil.ReadFile(testpath) if err != nil { t.Fatalf("Failed to read generated default config file: %v", err) } if !rpcuserRegexp.Match(content) { t.Error("Could not find rpcuser in generated default config file.") } if !rpcpassRegexp.Match(content) { t.Error("Could not find rpcpass in generated default config file.") } }