1// Copyright 2014 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package blueprint 16 17import ( 18 "reflect" 19 "strconv" 20 "strings" 21 "testing" 22) 23 24var ninjaParseTestCases = []struct { 25 input string 26 vars []string 27 strs []string 28 literal bool 29 err string 30}{ 31 { 32 input: "abc def $ghi jkl", 33 vars: []string{"ghi"}, 34 strs: []string{"abc def ", " jkl"}, 35 }, 36 { 37 input: "abc def $ghi$jkl", 38 vars: []string{"ghi", "jkl"}, 39 strs: []string{"abc def ", "", ""}, 40 }, 41 { 42 input: "foo $012_-345xyz_! bar", 43 vars: []string{"012_-345xyz_"}, 44 strs: []string{"foo ", "! bar"}, 45 }, 46 { 47 input: "foo ${012_-345xyz_} bar", 48 vars: []string{"012_-345xyz_"}, 49 strs: []string{"foo ", " bar"}, 50 }, 51 { 52 input: "foo ${012_-345xyz_} bar", 53 vars: []string{"012_-345xyz_"}, 54 strs: []string{"foo ", " bar"}, 55 }, 56 { 57 input: "foo $$ bar", 58 vars: nil, 59 strs: []string{"foo $$ bar"}, 60 // this is technically a literal, but not recognized as such due to the $$ 61 }, 62 { 63 input: "$foo${bar}", 64 vars: []string{"foo", "bar"}, 65 strs: []string{"", "", ""}, 66 }, 67 { 68 input: "$foo$$", 69 vars: []string{"foo"}, 70 strs: []string{"", "$$"}, 71 }, 72 { 73 input: "foo bar", 74 vars: nil, 75 strs: []string{"foo bar"}, 76 literal: true, 77 }, 78 { 79 input: " foo ", 80 vars: nil, 81 strs: []string{"$ foo "}, 82 literal: true, 83 }, 84 { 85 input: " $foo ", 86 vars: []string{"foo"}, 87 strs: []string{"$ ", " "}, 88 }, { 89 input: "foo $ bar", 90 err: "invalid character after '$' at byte offset 5", 91 }, 92 { 93 input: "foo $", 94 err: "unexpected end of string after '$'", 95 }, 96 { 97 input: "foo ${} bar", 98 err: "empty variable name at byte offset 6", 99 }, 100 { 101 input: "foo ${abc!} bar", 102 err: "invalid character in variable name at byte offset 9", 103 }, 104 { 105 input: "foo ${abc", 106 err: "unexpected end of string in variable name", 107 }, 108} 109 110func TestParseNinjaString(t *testing.T) { 111 for _, testCase := range ninjaParseTestCases { 112 scope := newLocalScope(nil, "namespace") 113 expectedVars := []Variable{} 114 for _, varName := range testCase.vars { 115 v, err := scope.LookupVariable(varName) 116 if err != nil { 117 v, err = scope.AddLocalVariable(varName, "") 118 if err != nil { 119 t.Fatalf("error creating scope: %s", err) 120 } 121 } 122 expectedVars = append(expectedVars, v) 123 } 124 125 var expected ninjaString 126 if len(testCase.strs) > 0 { 127 if testCase.literal { 128 expected = literalNinjaString(testCase.strs[0]) 129 } else { 130 expected = &varNinjaString{ 131 strings: testCase.strs, 132 variables: expectedVars, 133 } 134 } 135 } 136 137 output, err := parseNinjaString(scope, testCase.input) 138 if err == nil { 139 if !reflect.DeepEqual(output, expected) { 140 t.Errorf("incorrect ninja string:") 141 t.Errorf(" input: %q", testCase.input) 142 t.Errorf(" expected: %#v", expected) 143 t.Errorf(" got: %#v", output) 144 } 145 } 146 var errStr string 147 if err != nil { 148 errStr = err.Error() 149 } 150 if err != nil && err.Error() != testCase.err { 151 t.Errorf("unexpected error:") 152 t.Errorf(" input: %q", testCase.input) 153 t.Errorf(" expected: %q", testCase.err) 154 t.Errorf(" got: %q", errStr) 155 } 156 } 157} 158 159func TestParseNinjaStringWithImportedVar(t *testing.T) { 160 ImpVar := &staticVariable{name_: "ImpVar"} 161 impScope := newScope(nil) 162 impScope.AddVariable(ImpVar) 163 scope := newScope(nil) 164 scope.AddImport("impPkg", impScope) 165 166 input := "abc def ${impPkg.ImpVar} ghi" 167 output, err := parseNinjaString(scope, input) 168 if err != nil { 169 t.Fatalf("unexpected error: %s", err) 170 } 171 172 expect := []Variable{ImpVar} 173 if !reflect.DeepEqual(output.(*varNinjaString).variables, expect) { 174 t.Errorf("incorrect output:") 175 t.Errorf(" input: %q", input) 176 t.Errorf(" expected: %#v", expect) 177 t.Errorf(" got: %#v", output) 178 } 179} 180 181func BenchmarkNinjaString_Value(b *testing.B) { 182 b.Run("constant", func(b *testing.B) { 183 for _, l := range []int{1, 10, 100, 1000} { 184 ns := simpleNinjaString(strings.Repeat("a", l)) 185 b.Run(strconv.Itoa(l), func(b *testing.B) { 186 for n := 0; n < b.N; n++ { 187 ns.Value(nil) 188 } 189 }) 190 } 191 }) 192 b.Run("variable", func(b *testing.B) { 193 for _, l := range []int{1, 10, 100, 1000} { 194 scope := newLocalScope(nil, "") 195 scope.AddLocalVariable("a", strings.Repeat("b", l/3)) 196 ns, _ := parseNinjaString(scope, strings.Repeat("a", l/3)+"${a}"+strings.Repeat("a", l/3)) 197 b.Run(strconv.Itoa(l), func(b *testing.B) { 198 for n := 0; n < b.N; n++ { 199 ns.Value(nil) 200 } 201 }) 202 } 203 }) 204 b.Run("variables", func(b *testing.B) { 205 for _, l := range []int{1, 2, 3, 4, 5, 10, 100, 1000} { 206 scope := newLocalScope(nil, "") 207 str := strings.Repeat("a", 10) 208 for i := 0; i < l; i++ { 209 scope.AddLocalVariable("a"+strconv.Itoa(i), strings.Repeat("b", 10)) 210 str += "${a" + strconv.Itoa(i) + "}" 211 } 212 ns, _ := parseNinjaString(scope, str) 213 b.Run(strconv.Itoa(l), func(b *testing.B) { 214 for n := 0; n < b.N; n++ { 215 ns.Value(nil) 216 } 217 }) 218 } 219 }) 220 221} 222