github.com/magnusbaeck/logstash-filter-verifier/v2@v2.0.0-pre.1/logstash/invocation_test.go (about) 1 // Copyright (c) 2017 Magnus Bäck <magnus@noun.se> 2 3 package logstash 4 5 import ( 6 "errors" 7 "fmt" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "testing" 12 13 semver "github.com/Masterminds/semver/v3" 14 "github.com/magnusbaeck/logstash-filter-verifier/testhelpers" 15 ) 16 17 func TestArgs(t *testing.T) { 18 tinv, err := createTestInvocation(*semver.MustParse("6.0.0")) 19 if err != nil { 20 t.Fatalf("%s", err) 21 } 22 23 args, err := tinv.Inv.Args("input", "output") 24 if err != nil { 25 t.Fatalf("Error generating args: %s", err) 26 } 27 options := simpleOptionParser(args) 28 configOption, exists := options["-f"] 29 if !exists { 30 t.Fatalf("no -f option found") 31 } 32 configDir, err := os.Open(configOption) 33 if err != nil { 34 t.Fatalf("Error opening configuration file directory: %s", err) 35 } 36 37 files, err := configDir.Readdirnames(0) 38 if err != nil { 39 t.Fatalf("Error reading configuration file directory: %s", err) 40 } 41 42 // Three aspects of the pipeline config file directory concern us: 43 // - The file that normally contains filters exists and has the 44 // expected contents. 45 // - The file with the inputs and outputs exists and has the 46 // expected contents. 47 // - No other files are present. 48 var filterOk bool 49 var ioOk bool 50 for _, file := range files { 51 buf, err := ioutil.ReadFile(filepath.Join(configOption, file)) 52 if err != nil { 53 t.Errorf("Error reading configuration file: %s", err) 54 continue 55 } 56 fileContents := string(buf) 57 58 // Filter configuration file. 59 if file == tinv.configFile { 60 if fileContents != tinv.configContents { 61 t.Errorf("Filter configuration file didn't contain the expected data.\nExpected: %q\nGot: %q", tinv.configContents, fileContents) 62 } 63 filterOk = true 64 continue 65 } 66 67 // Input/Output configuration file. 68 if file == filepath.Base(tinv.Inv.ioConfigFile.Name()) { 69 expectedIoConfig := "input\noutput" 70 if fileContents != expectedIoConfig { 71 t.Errorf("Input/output configuration file didn't contain the expected data.\nExpected: %q\nGot: %q", 72 expectedIoConfig, fileContents) 73 } 74 ioOk = true 75 continue 76 } 77 78 // We should never get here. 79 t.Errorf("Unexpected file found: %s", file) 80 } 81 82 if !filterOk { 83 t.Errorf("No filter configuration file found in %s: %v", configOption, files) 84 } 85 if !ioOk { 86 t.Errorf("No input/output configuration file found in %s: %v", configOption, files) 87 } 88 } 89 90 func TestNewInvocation(t *testing.T) { 91 cases := []struct { 92 version string 93 optionTests func(options map[string]string) error 94 }{ 95 // Logstash 2.4 gets a regular file as a log file argument. 96 { 97 "2.4.0", 98 func(options map[string]string) error { 99 logOption, exists := options["-l"] 100 if !exists { 101 return errors.New("no logfile option found") 102 } 103 fi, err := os.Stat(logOption) 104 if err != nil { 105 return fmt.Errorf("could not stat logfile: %s", err) 106 } 107 if !fi.Mode().IsRegular() { 108 return fmt.Errorf("log path not a regular file: %s", fi.Name()) 109 } 110 111 if _, exists = options["--path.settings"]; exists { 112 return errors.New("unsupported --path.settings option provided") 113 } 114 return nil 115 }, 116 }, 117 // Logstash 5.0 gets a directory as a log file 118 // argument and --path.settings pointing to a 119 // directory with the expected files. 120 { 121 "5.0.0", 122 func(options map[string]string) error { 123 logOption, exists := options["-l"] 124 if !exists { 125 return errors.New("no logfile option found") 126 } 127 fi, err := os.Stat(logOption) 128 if err != nil { 129 return fmt.Errorf("could not stat logfile: %s", err) 130 } 131 if fi.Mode().IsRegular() { 132 return fmt.Errorf("log path not a regular file: %s", fi.Name()) 133 } 134 135 pathOption, exists := options["--path.settings"] 136 if !exists { 137 return errors.New("--path.settings option missing") 138 } 139 requiredFiles := []string{ 140 "jvm.options", 141 "log4j2.properties", 142 "logstash.yml", 143 } 144 if !allFilesExist(pathOption, requiredFiles) { 145 return fmt.Errorf("Not all required files found in %q: %v", 146 pathOption, requiredFiles) 147 } 148 149 return nil 150 }, 151 }, 152 } 153 for i, c := range cases { 154 tinv, err := createTestInvocation(*semver.MustParse(c.version)) 155 if err != nil { 156 t.Errorf("Test %d: Error unexpectedly returned: %s", i, err) 157 continue 158 } 159 defer tinv.Release() 160 161 args, err := tinv.Inv.Args("input", "output") 162 if err != nil { 163 t.Errorf("Test %d: Error generating args: %s", i, err) 164 } 165 err = c.optionTests(simpleOptionParser(args)) 166 if err != nil { 167 t.Errorf("Test %d: Command option test failed for %v: %s", i, args, err) 168 } 169 } 170 } 171 172 type testInvocation struct { 173 Inv *Invocation 174 tempdir string 175 configFile string 176 configContents string 177 } 178 179 func createTestInvocation(version semver.Version) (*testInvocation, error) { 180 tempdir, err := ioutil.TempDir("", "") 181 if err != nil { 182 return nil, fmt.Errorf("Unexpected error when creating temp dir: %s", err) 183 } 184 185 files := []testhelpers.FileWithMode{ 186 {"bin", os.ModeDir | 0755, ""}, 187 {"bin/logstash", 0755, ""}, 188 {"config", os.ModeDir | 0755, ""}, 189 {"config/jvm.options", 0644, ""}, 190 {"config/log4j2.properties", 0644, ""}, 191 } 192 for _, fwm := range files { 193 if err = fwm.Create(tempdir); err != nil { 194 return nil, fmt.Errorf("Unexpected error when creating test file: %s", err) 195 } 196 } 197 198 configFile := filepath.Join(tempdir, "configfile.conf") 199 configContents := "" 200 if err = ioutil.WriteFile(configFile, []byte(configContents), 0600); err != nil { 201 return nil, fmt.Errorf("Unexpected error when creating dummy configuration file: %s", err) 202 } 203 logstashPath := filepath.Join(tempdir, "bin/logstash") 204 inv, err := NewInvocation(logstashPath, []string{}, &version, configFile) 205 if err != nil { 206 return nil, fmt.Errorf("Unexpected error when creating Invocation: %s", err) 207 } 208 209 return &testInvocation{inv, tempdir, filepath.Base(configFile), configContents}, nil 210 } 211 212 func (ti *testInvocation) Release() { 213 ti.Inv.Release() 214 _ = os.RemoveAll(ti.tempdir) 215 } 216 217 // simpleOptionParser is a super-simple command line option parser 218 // that just builds a map of all the options and their values. For 219 // options not taking any arguments the option's value will be an 220 // empty string. 221 func simpleOptionParser(args []string) map[string]string { 222 result := map[string]string{} 223 for i := 0; i < len(args); i++ { 224 if args[i][0] != '-' { 225 continue 226 } 227 228 if i+1 < len(args) && args[i+1][0] != '-' { 229 result[args[i]] = args[i+1] 230 i++ 231 } else { 232 result[args[i]] = "" 233 } 234 } 235 return result 236 }