github.com/abayer/test-infra@v0.0.5/mungegithub/options/options_test.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package options
    18  
    19  import (
    20  	"bufio"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"os"
    24  	"reflect"
    25  	"strings"
    26  	"testing"
    27  	"time"
    28  
    29  	"k8s.io/apimachinery/pkg/util/sets"
    30  )
    31  
    32  var (
    33  	str1, str2     string
    34  	slice1, slice2 []string
    35  	int1           int
    36  	uint1          uint64
    37  	bool1          bool
    38  	bool2          bool
    39  	dur1           time.Duration
    40  
    41  	entries = []optEntry{
    42  		{key: "strvar", val: "def", ptr: &str1},
    43  		{key: "strslicevar", val: []string{"def", "def2"}, ptr: &slice1},
    44  		{key: "intvar", val: 5, ptr: &int1},
    45  		{key: "uintvar", val: uint64(5), ptr: &uint1},
    46  		{key: "boolvar", val: true, ptr: &bool1},
    47  		{key: "boolvar2", val: false, ptr: &bool2},
    48  		{key: "durvar", val: time.Second * 2, ptr: &dur1},
    49  		{key: "emptyslicevar", val: []string{"def"}, ptr: &slice2},
    50  		{key: "emptystringvar", val: "def", ptr: &str2},
    51  	}
    52  
    53  	sample_yaml = `strvar: Hello world.
    54  strslicevar: Hello world., string2
    55  intvar: 5
    56  uintvar: 6
    57  boolvar: false
    58  boolvar2: true
    59  durvar: 15s
    60  emptyslicevar:
    61  emptystringvar: ""`
    62  	expected = []optEntry{
    63  		{key: "strvar", val: "Hello world.", ptr: &str1},
    64  		{key: "strslicevar", val: []string{"Hello world.", "string2"}, ptr: &slice1},
    65  		{key: "intvar", val: 5, ptr: &int1},
    66  		{key: "uintvar", val: uint64(6), ptr: &uint1},
    67  		{key: "boolvar", val: false, ptr: &bool1},
    68  		{key: "boolvar2", val: true, ptr: &bool2},
    69  		{key: "durvar", val: time.Second * 15, ptr: &dur1},
    70  		{key: "emptyslicevar", val: []string{}, ptr: &slice2},
    71  		{key: "emptystringvar", val: "", ptr: &str2},
    72  	}
    73  
    74  	sample_yaml2 = `intvar: 700
    75  durvar: 5s
    76  emptyslicevar: ""`
    77  	expected2 = []optEntry{
    78  		{key: "strvar", val: "def", ptr: &str1},
    79  		{key: "strslicevar", val: []string{"def", "def2"}, ptr: &slice1},
    80  		{key: "intvar", val: 700, ptr: &int1},
    81  		{key: "uintvar", val: uint64(5), ptr: &uint1},
    82  		{key: "boolvar", val: true, ptr: &bool1},
    83  		{key: "boolvar2", val: false, ptr: &bool2},
    84  		{key: "durvar", val: time.Second * 5, ptr: &dur1},
    85  		{key: "emptyslicevar", val: []string{}, ptr: &slice2},
    86  		{key: "emptystringvar", val: "def", ptr: &str2},
    87  	}
    88  )
    89  
    90  type tempConfig struct {
    91  	file   *os.File
    92  	writer *bufio.Writer
    93  }
    94  
    95  func newTempConfig() (*tempConfig, error) {
    96  	tempfile, err := ioutil.TempFile(os.TempDir(), "options_test")
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	return &tempConfig{file: tempfile, writer: bufio.NewWriter(tempfile)}, nil
   101  }
   102  
   103  func (t *tempConfig) SetContent(content string) error {
   104  	// Clear file and reset writing offset
   105  	t.file.Truncate(0)
   106  	t.file.Seek(0, os.SEEK_SET)
   107  	t.writer.Reset(t.file)
   108  	if _, err := t.writer.WriteString(content); err != nil {
   109  		return err
   110  	}
   111  	if err := t.writer.Flush(); err != nil {
   112  		return err
   113  	}
   114  	return nil
   115  }
   116  
   117  func (t *tempConfig) Clean() {
   118  	t.file.Close()
   119  	os.Remove(t.file.Name())
   120  }
   121  
   122  type optEntry struct {
   123  	key string
   124  	val interface{}
   125  	ptr interface{}
   126  }
   127  
   128  func registerAll(opts *Options) {
   129  	for _, entry := range entries {
   130  		switch defVal := entry.val.(type) {
   131  		case string:
   132  			opts.RegisterString(entry.ptr.(*string), entry.key, defVal, "Desc: "+entry.key)
   133  		case []string:
   134  			opts.RegisterStringSlice(entry.ptr.(*[]string), entry.key, defVal, "Desc: "+entry.key)
   135  		case int:
   136  			opts.RegisterInt(entry.ptr.(*int), entry.key, defVal, "Desc: "+entry.key)
   137  		case uint64:
   138  			opts.RegisterUint64(entry.ptr.(*uint64), entry.key, defVal, "Desc: "+entry.key)
   139  		case bool:
   140  			opts.RegisterBool(entry.ptr.(*bool), entry.key, defVal, "Desc: "+entry.key)
   141  		case time.Duration:
   142  			opts.RegisterDuration(entry.ptr.(*time.Duration), entry.key, defVal, "Desc: "+entry.key)
   143  		}
   144  	}
   145  }
   146  
   147  func checkAll(opts *Options, expected []optEntry) error {
   148  	for _, entry := range expected {
   149  		switch expectedVal := entry.val.(type) {
   150  		case string:
   151  			v := opts.GetString(entry.key)
   152  			if v != entry.ptr {
   153  				return fmt.Errorf("string opt '%s' moved! Was at %p, is now at %p", entry.key, entry.ptr, v)
   154  			}
   155  			if *v != expectedVal {
   156  				return fmt.Errorf("string opt '%s' has value %q, expected %q", entry.key, *v, expectedVal)
   157  			}
   158  		case []string:
   159  			v := opts.GetStringSlice(entry.key)
   160  			if v != entry.ptr {
   161  				return fmt.Errorf("[]string opt '%s' moved! Was at %p, is now at %p", entry.key, entry.ptr, v)
   162  			}
   163  			if !reflect.DeepEqual(*v, expectedVal) {
   164  				return fmt.Errorf("[]string opt '%s' has value %q, expected %q", entry.key, *v, expectedVal)
   165  			}
   166  		case int:
   167  			v := opts.GetInt(entry.key)
   168  			if v != entry.ptr {
   169  				return fmt.Errorf("int opt '%s' moved! Was at %p, is now at %p", entry.key, entry.ptr, v)
   170  			}
   171  			if *v != expectedVal {
   172  				return fmt.Errorf("int opt '%s' has value %d, expected %d", entry.key, *v, expectedVal)
   173  			}
   174  		case uint64:
   175  			v := opts.GetUint64(entry.key)
   176  			if v != entry.ptr {
   177  				return fmt.Errorf("uint64 opt '%s' moved! Was at %p, is now at %p", entry.key, entry.ptr, v)
   178  			}
   179  			if *v != expectedVal {
   180  				return fmt.Errorf("uint64 opt '%s' has value %d, expected %d", entry.key, *v, expectedVal)
   181  			}
   182  		case bool:
   183  			v := opts.GetBool(entry.key)
   184  			if v != entry.ptr {
   185  				return fmt.Errorf("bool opt '%s' moved! Was at %p, is now at %p", entry.key, entry.ptr, v)
   186  			}
   187  			if *v != expectedVal {
   188  				return fmt.Errorf("bool opt '%s' has value %v, expected %v", entry.key, *v, expectedVal)
   189  			}
   190  		case time.Duration:
   191  			v := opts.GetDuration(entry.key)
   192  			if v != entry.ptr {
   193  				return fmt.Errorf("duration opt '%s' moved! Was at %p, is now at %p", entry.key, entry.ptr, v)
   194  			}
   195  			if *v != expectedVal {
   196  				return fmt.Errorf("duration opt '%s' has value %v, expected %v", entry.key, *v, expectedVal)
   197  			}
   198  		}
   199  	}
   200  	return nil
   201  }
   202  
   203  // TestOptions test that all option types are recalled properly.
   204  // Specifically, options are checked to be correct after various combinations and orderings of
   205  // loads, registers, reloads, and reregisters.
   206  func TestOptions(t *testing.T) {
   207  	cfg, err := newTempConfig()
   208  	if err != nil {
   209  		t.Error(err)
   210  	}
   211  	defer cfg.Clean()
   212  
   213  	if err = cfg.SetContent(sample_yaml); err != nil {
   214  		t.Error(err)
   215  	}
   216  	// Test simple load + register in both orders.
   217  	oLoadFirst := New()
   218  	oRegisterFirst := New()
   219  	oRegisterOnce := New()
   220  
   221  	if _, err = oLoadFirst.Load(cfg.file.Name()); err != nil {
   222  		t.Errorf("Load failed: %v", err)
   223  	}
   224  	registerAll(oLoadFirst)
   225  	registerAll(oRegisterFirst)
   226  	if _, err = oRegisterFirst.Load(cfg.file.Name()); err != nil {
   227  		t.Errorf("Load failed: %v", err)
   228  	}
   229  	//Test Register only (no Load).
   230  	registerAll(oRegisterOnce)
   231  	if err = checkAll(oRegisterOnce, entries); err != nil {
   232  		t.Errorf("checkAll failed: %v", err)
   233  	}
   234  	if _, err = oRegisterOnce.Load(cfg.file.Name()); err != nil {
   235  		t.Errorf("Load failed: %v", err)
   236  	}
   237  
   238  	if err = checkAll(oLoadFirst, expected); err != nil {
   239  		t.Errorf("checkAll failed: %v", err)
   240  	}
   241  	if err = checkAll(oRegisterFirst, expected); err != nil {
   242  		t.Errorf("checkAll failed: %v", err)
   243  	}
   244  	if err = checkAll(oRegisterOnce, expected); err != nil {
   245  		t.Errorf("checkAll failed: %v", err)
   246  	}
   247  
   248  	if err = cfg.SetContent(sample_yaml2); err != nil {
   249  		t.Error(err)
   250  	}
   251  	// Test back to back loads and registers.
   252  	registerAll(oLoadFirst)
   253  	if _, err = oLoadFirst.Load(cfg.file.Name()); err != nil {
   254  		t.Errorf("Load failed: %v", err)
   255  	}
   256  	if _, err = oRegisterFirst.Load(cfg.file.Name()); err != nil {
   257  		t.Errorf("Load failed: %v", err)
   258  	}
   259  	registerAll(oRegisterFirst)
   260  	// Test reload without reregister.
   261  	if _, err = oRegisterOnce.Load(cfg.file.Name()); err != nil {
   262  		t.Errorf("Load failed: %v", err)
   263  	}
   264  
   265  	if err = checkAll(oLoadFirst, expected2); err != nil {
   266  		t.Errorf("checkAll failed: %v", err)
   267  	}
   268  	if err = checkAll(oRegisterFirst, expected2); err != nil {
   269  		t.Errorf("checkAll failed: %v", err)
   270  	}
   271  	if err = checkAll(oRegisterOnce, expected2); err != nil {
   272  		t.Errorf("checkAll failed: %v", err)
   273  	}
   274  
   275  	// Test no-op reload of same config (sample_yaml2 again).
   276  	if _, err = oRegisterOnce.Load(cfg.file.Name()); err != nil {
   277  		t.Errorf("Load failed: %v", err)
   278  	}
   279  	if err = checkAll(oRegisterOnce, expected2); err != nil {
   280  		t.Errorf("checkAll failed: %v", err)
   281  	}
   282  }
   283  
   284  // TestDefaults verifies that Options use their default values when a value is not specified.
   285  func TestDefaults(t *testing.T) {
   286  	cfg, err := newTempConfig()
   287  	if err != nil {
   288  		t.Error(err)
   289  	}
   290  	defer cfg.Clean()
   291  
   292  	if err = cfg.SetContent(sample_yaml2); err != nil {
   293  		t.Error(err)
   294  	}
   295  
   296  	// Test Load then Register.
   297  	oUseDefaults := New()
   298  	if _, err = oUseDefaults.Load(cfg.file.Name()); err != nil {
   299  		t.Errorf("Load failed: %v", err)
   300  	}
   301  	registerAll(oUseDefaults)
   302  
   303  	if err = checkAll(oUseDefaults, expected2); err != nil {
   304  		t.Errorf("checkAll failed: %v", err)
   305  	}
   306  	// Test Register then Load.
   307  	oUseDefaults = New()
   308  	registerAll(oUseDefaults)
   309  	if _, err = oUseDefaults.Load(cfg.file.Name()); err != nil {
   310  		t.Errorf("Load failed: %v", err)
   311  	}
   312  	if err = checkAll(oUseDefaults, expected2); err != nil {
   313  		t.Errorf("checkAll failed: %v", err)
   314  	}
   315  
   316  	// Test that an option reverts back to default if a value is no longer specified after a reload.
   317  	if err = cfg.SetContent(sample_yaml); err != nil {
   318  		t.Error(err)
   319  	}
   320  	if _, err = oUseDefaults.Load(cfg.file.Name()); err != nil {
   321  		t.Errorf("Load failed: %v", err)
   322  	}
   323  	if err = checkAll(oUseDefaults, expected); err != nil {
   324  		t.Errorf("checkAll failed: %v", err)
   325  	}
   326  	if err = cfg.SetContent(sample_yaml2); err != nil {
   327  		t.Error(err)
   328  	}
   329  	if _, err = oUseDefaults.Load(cfg.file.Name()); err != nil {
   330  		t.Errorf("Load failed: %v", err)
   331  	}
   332  	if err = checkAll(oUseDefaults, expected2); err != nil {
   333  		t.Errorf("checkAll failed: %v", err)
   334  	}
   335  }
   336  
   337  func TestDescriptionsAndToString(t *testing.T) {
   338  	cfg, err := newTempConfig()
   339  	if err != nil {
   340  		t.Error(err)
   341  	}
   342  	defer cfg.Clean()
   343  
   344  	if err = cfg.SetContent(sample_yaml); err != nil {
   345  		t.Error(err)
   346  	}
   347  
   348  	o := New()
   349  	registerAll(o)
   350  	desc := o.Descriptions()
   351  	for _, entry := range entries {
   352  		if !strings.Contains(desc, "\"Desc: "+entry.key+"\"") ||
   353  			!strings.Contains(desc, entry.key) {
   354  			t.Errorf("Description is malformed for key '%s':\n%s\n", entry.key, desc)
   355  		}
   356  	}
   357  	// Check that default values are formatted as expected.
   358  	checks := []string{"(\"def\")", "([\"def\", \"def2\"])", "(2s)", "(5)", "(true)"}
   359  	for _, check := range checks {
   360  		if !strings.Contains(desc, check) {
   361  			t.Errorf("Description does not contain default value '%s':\n%s\n", check, desc)
   362  		}
   363  	}
   364  }
   365  
   366  func TestUpdateCallback(t *testing.T) {
   367  	cfg, err := newTempConfig()
   368  	if err != nil {
   369  		t.Error(err)
   370  	}
   371  	defer cfg.Clean()
   372  
   373  	if err = cfg.SetContent(sample_yaml); err != nil {
   374  		t.Error(err)
   375  	}
   376  
   377  	o := New()
   378  	registerAll(o)
   379  	o.RegisterUpdateCallback(func(changed sets.String) error {
   380  		return fmt.Errorf("some error")
   381  	})
   382  	if _, err = o.Load(cfg.file.Name()); err != nil {
   383  		if _, ok := err.(*UpdateCallbackError); ok {
   384  			t.Errorf("Unexpected UpdateCallbackError from first load (should not have called callback): %v", err)
   385  		} else {
   386  			t.Errorf("Load failed: %v", err)
   387  		}
   388  	}
   389  
   390  	if err = cfg.SetContent(sample_yaml2); err != nil {
   391  		t.Error(err)
   392  	}
   393  	if _, err = o.Load(cfg.file.Name()); err == nil {
   394  		t.Errorf("Expected an UpdateCallbackError but no error was returned.")
   395  	} else if _, ok := err.(*UpdateCallbackError); !ok {
   396  		t.Errorf("Expected an UpdateCallbackError but got a different error: %v", err)
   397  	}
   398  }
   399  
   400  func TestChanged(t *testing.T) {
   401  	cfg, err := newTempConfig()
   402  	if err != nil {
   403  		t.Error(err)
   404  	}
   405  	defer cfg.Clean()
   406  
   407  	if err = cfg.SetContent(sample_yaml); err != nil {
   408  		t.Error(err)
   409  	}
   410  
   411  	o := New()
   412  	registerAll(o)
   413  	var changed sets.String
   414  	if changed, err = o.Load(cfg.file.Name()); err != nil {
   415  		t.Errorf("Load failed: %v", err)
   416  	}
   417  
   418  	if err = cfg.SetContent(sample_yaml2); err != nil {
   419  		t.Error(err)
   420  	}
   421  	expectedChanges := sets.NewString("durvar", "intvar", "strvar", "strslicevar", "uintvar", "boolvar", "boolvar2", "emptystringvar")
   422  	if changed, err = o.Load(cfg.file.Name()); err != nil {
   423  		t.Errorf("Load failed: %v", err)
   424  	}
   425  	if !changed.Equal(expectedChanges) {
   426  		t.Errorf(
   427  			"Error: expected options %q to be changed, but %q were changed.",
   428  			expectedChanges.List(),
   429  			changed.List(),
   430  		)
   431  	}
   432  }