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  }