github.com/psiphon-inc/goarista@v0.0.0-20160825065156-d002785f4c67/pathmap/pathmap_test.go (about)

     1  // Copyright (C) 2016  Arista Networks, Inc.
     2  // Use of this source code is governed by the Apache License 2.0
     3  // that can be found in the COPYING file.
     4  
     5  package pathmap
     6  
     7  import (
     8  	"errors"
     9  	"testing"
    10  
    11  	"github.com/aristanetworks/goarista/test"
    12  )
    13  
    14  func accumulator(counter map[int]int) VisitorFunc {
    15  	return func(val interface{}) error {
    16  		counter[val.(int)]++
    17  		return nil
    18  	}
    19  }
    20  
    21  func TestVisit(t *testing.T) {
    22  	m := New()
    23  	m.Set([]string{"foo", "bar", "baz"}, 1)
    24  	m.Set([]string{"*", "bar", "baz"}, 2)
    25  	m.Set([]string{"*", "*", "baz"}, 3)
    26  	m.Set([]string{"*", "*", "*"}, 4)
    27  	m.Set([]string{"foo", "*", "*"}, 5)
    28  	m.Set([]string{"foo", "bar", "*"}, 6)
    29  	m.Set([]string{"foo", "*", "baz"}, 7)
    30  	m.Set([]string{"*", "bar", "*"}, 8)
    31  
    32  	m.Set([]string{}, 10)
    33  
    34  	m.Set([]string{"*"}, 20)
    35  	m.Set([]string{"foo"}, 21)
    36  
    37  	m.Set([]string{"zap", "zip"}, 30)
    38  	m.Set([]string{"zap", "zip"}, 31)
    39  
    40  	m.Set([]string{"zip", "*"}, 40)
    41  	m.Set([]string{"zip", "*"}, 41)
    42  
    43  	testCases := []struct {
    44  		path     []string
    45  		expected map[int]int
    46  	}{{
    47  		path:     []string{"foo", "bar", "baz"},
    48  		expected: map[int]int{1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1},
    49  	}, {
    50  		path:     []string{"qux", "bar", "baz"},
    51  		expected: map[int]int{2: 1, 3: 1, 4: 1, 8: 1},
    52  	}, {
    53  		path:     []string{"foo", "qux", "baz"},
    54  		expected: map[int]int{3: 1, 4: 1, 5: 1, 7: 1},
    55  	}, {
    56  		path:     []string{"foo", "bar", "qux"},
    57  		expected: map[int]int{4: 1, 5: 1, 6: 1, 8: 1},
    58  	}, {
    59  		path:     []string{},
    60  		expected: map[int]int{10: 1},
    61  	}, {
    62  		path:     []string{"foo"},
    63  		expected: map[int]int{20: 1, 21: 1},
    64  	}, {
    65  		path:     []string{"foo", "bar"},
    66  		expected: map[int]int{},
    67  	}, {
    68  		path:     []string{"zap", "zip"},
    69  		expected: map[int]int{31: 1},
    70  	}, {
    71  		path:     []string{"zip", "zap"},
    72  		expected: map[int]int{41: 1},
    73  	}}
    74  
    75  	for _, tc := range testCases {
    76  		result := make(map[int]int, len(tc.expected))
    77  		m.Visit(tc.path, accumulator(result))
    78  		if diff := test.Diff(tc.expected, result); diff != "" {
    79  			t.Errorf("Test case %v: %s", tc.path, diff)
    80  		}
    81  	}
    82  }
    83  
    84  func TestVisitError(t *testing.T) {
    85  	m := New()
    86  	m.Set([]string{"foo", "bar"}, 1)
    87  	m.Set([]string{"*", "bar"}, 2)
    88  
    89  	errTest := errors.New("Test")
    90  
    91  	err := m.Visit([]string{"foo", "bar"}, func(v interface{}) error { return errTest })
    92  	if err != errTest {
    93  		t.Errorf("Unexpected error. Expected: %v, Got: %v", errTest, err)
    94  	}
    95  }
    96  
    97  func TestGet(t *testing.T) {
    98  	m := New()
    99  	m.Set([]string{}, 0)
   100  	m.Set([]string{"foo", "bar"}, 1)
   101  	m.Set([]string{"foo", "*"}, 2)
   102  	m.Set([]string{"*", "bar"}, 3)
   103  	m.Set([]string{"zap", "zip"}, 4)
   104  
   105  	testCases := []struct {
   106  		path     []string
   107  		expected interface{}
   108  	}{{
   109  		path:     []string{},
   110  		expected: 0,
   111  	}, {
   112  		path:     []string{"foo", "bar"},
   113  		expected: 1,
   114  	}, {
   115  		path:     []string{"foo", "*"},
   116  		expected: 2,
   117  	}, {
   118  		path:     []string{"*", "bar"},
   119  		expected: 3,
   120  	}, {
   121  		path:     []string{"bar", "foo"},
   122  		expected: nil,
   123  	}, {
   124  		path:     []string{"zap", "*"},
   125  		expected: nil,
   126  	}}
   127  
   128  	for _, tc := range testCases {
   129  		got := m.Get(tc.path)
   130  		if got != tc.expected {
   131  			t.Errorf("Test case %v: Expected %v, Got %v",
   132  				tc.path, tc.expected, got)
   133  		}
   134  	}
   135  }
   136  
   137  func countNodes(n *node) int {
   138  	if n == nil {
   139  		return 0
   140  	}
   141  	count := 1
   142  	count += countNodes(n.wildcard)
   143  	for _, child := range n.children {
   144  		count += countNodes(child)
   145  	}
   146  	return count
   147  }
   148  
   149  func TestDelete(t *testing.T) {
   150  	m := New()
   151  	m.Set([]string{}, 0)
   152  	m.Set([]string{"*"}, 1)
   153  	m.Set([]string{"foo", "bar"}, 2)
   154  	m.Set([]string{"foo", "*"}, 3)
   155  
   156  	n := countNodes(m.(*node))
   157  	if n != 5 {
   158  		t.Errorf("Initial count wrong. Expected: 5, Got: %d", n)
   159  	}
   160  
   161  	testCases := []struct {
   162  		del      []string    // Path to delete
   163  		expected bool        // expected return value of Delete
   164  		visit    []string    // Path to Visit
   165  		before   map[int]int // Expected to find items before deletion
   166  		after    map[int]int // Expected to find items after deletion
   167  		count    int         // Count of nodes
   168  	}{{
   169  		del:      []string{"zap"}, // A no-op Delete
   170  		expected: false,
   171  		visit:    []string{"foo", "bar"},
   172  		before:   map[int]int{2: 1, 3: 1},
   173  		after:    map[int]int{2: 1, 3: 1},
   174  		count:    5,
   175  	}, {
   176  		del:      []string{"foo", "bar"},
   177  		expected: true,
   178  		visit:    []string{"foo", "bar"},
   179  		before:   map[int]int{2: 1, 3: 1},
   180  		after:    map[int]int{3: 1},
   181  		count:    4,
   182  	}, {
   183  		del:      []string{"*"},
   184  		expected: true,
   185  		visit:    []string{"foo"},
   186  		before:   map[int]int{1: 1},
   187  		after:    map[int]int{},
   188  		count:    3,
   189  	}, {
   190  		del:      []string{"*"},
   191  		expected: false,
   192  		visit:    []string{"foo"},
   193  		before:   map[int]int{},
   194  		after:    map[int]int{},
   195  		count:    3,
   196  	}, {
   197  		del:      []string{"foo", "*"},
   198  		expected: true,
   199  		visit:    []string{"foo", "bar"},
   200  		before:   map[int]int{3: 1},
   201  		after:    map[int]int{},
   202  		count:    1, // Should have deleted "foo" and "bar" nodes
   203  	}, {
   204  		del:      []string{},
   205  		expected: true,
   206  		visit:    []string{},
   207  		before:   map[int]int{0: 1},
   208  		after:    map[int]int{},
   209  		count:    1, // Root node can't be deleted
   210  	}}
   211  
   212  	for i, tc := range testCases {
   213  		beforeResult := make(map[int]int, len(tc.before))
   214  		m.Visit(tc.visit, accumulator(beforeResult))
   215  		if diff := test.Diff(tc.before, beforeResult); diff != "" {
   216  			t.Errorf("Test case %d (%v): %s", i, tc.del, diff)
   217  		}
   218  
   219  		if got := m.Delete(tc.del); got != tc.expected {
   220  			t.Errorf("Test case %d (%v): Unexpected return. Expected %t, Got: %t",
   221  				i, tc.del, tc.expected, got)
   222  		}
   223  
   224  		afterResult := make(map[int]int, len(tc.after))
   225  		m.Visit(tc.visit, accumulator(afterResult))
   226  		if diff := test.Diff(tc.after, afterResult); diff != "" {
   227  			t.Errorf("Test case %d (%v): %s", i, tc.del, diff)
   228  		}
   229  	}
   230  }
   231  
   232  func genWords(count, wordLength int) []string {
   233  	chars := []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
   234  	if count+wordLength > len(chars) {
   235  		panic("need more chars")
   236  	}
   237  	result := make([]string, count)
   238  	for i := 0; i < count; i++ {
   239  		result[i] = string(chars[i : i+wordLength])
   240  	}
   241  	return result
   242  }
   243  
   244  func benchmarkPathMap(pathLength, pathDepth int, b *testing.B) {
   245  	m := New()
   246  
   247  	// Push pathDepth paths, each of length pathLength
   248  	path := genWords(pathLength, 10)
   249  	words := genWords(pathDepth, 10)
   250  	n := m.(*node)
   251  	for _, element := range path {
   252  		n.children = map[string]*node{}
   253  		for _, word := range words {
   254  			n.children[word] = &node{}
   255  		}
   256  		n = n.children[element]
   257  	}
   258  	b.ResetTimer()
   259  	for i := 0; i < b.N; i++ {
   260  		m.Visit(path, func(v interface{}) error { return nil })
   261  	}
   262  }
   263  
   264  func BenchmarkPathMap1x25(b *testing.B)  { benchmarkPathMap(1, 25, b) }
   265  func BenchmarkPathMap10x50(b *testing.B) { benchmarkPathMap(10, 25, b) }
   266  func BenchmarkPathMap20x50(b *testing.B) { benchmarkPathMap(20, 25, b) }