From fe1637c2236e6d0157898ba8166377d99b3671a5 Mon Sep 17 00:00:00 2001 From: Jonathan Moody <103143855+moodyjon@users.noreply.github.com> Date: Wed, 20 Apr 2022 10:33:36 -0400 Subject: [PATCH] [lbry] config: Verify completeness of sample-lbcd.conf using reflection on config struct. --- config_test.go | 104 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/config_test.go b/config_test.go index 84e7d444..15953b15 100644 --- a/config_test.go +++ b/config_test.go @@ -1,9 +1,11 @@ package main import ( + "fmt" "io/ioutil" "os" "path/filepath" + "reflect" "regexp" "runtime" "testing" @@ -14,6 +16,108 @@ var ( 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)