1// Copyright 2013 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package constant
6
7import (
8	"fmt"
9	"go/token"
10	"strings"
11	"testing"
12)
13
14// TODO(gri) expand this test framework
15
16var opTests = []string{
17	// unary operations
18	`+ 0 = 0`,
19	`+ ? = ?`,
20	`- 1 = -1`,
21	`- ? = ?`,
22	`^ 0 = -1`,
23	`^ ? = ?`,
24
25	`! true = false`,
26	`! false = true`,
27	`! ? = ?`,
28
29	// etc.
30
31	// binary operations
32	`"" + "" = ""`,
33	`"foo" + "" = "foo"`,
34	`"" + "bar" = "bar"`,
35	`"foo" + "bar" = "foobar"`,
36
37	`0 + 0 = 0`,
38	`0 + 0.1 = 0.1`,
39	`0 + 0.1i = 0.1i`,
40	`0.1 + 0.9 = 1`,
41	`1e100 + 1e100 = 2e100`,
42	`? + 0 = ?`,
43	`0 + ? = ?`,
44
45	`0 - 0 = 0`,
46	`0 - 0.1 = -0.1`,
47	`0 - 0.1i = -0.1i`,
48	`1e100 - 1e100 = 0`,
49	`? - 0 = ?`,
50	`0 - ? = ?`,
51
52	`0 * 0 = 0`,
53	`1 * 0.1 = 0.1`,
54	`1 * 0.1i = 0.1i`,
55	`1i * 1i = -1`,
56	`? * 0 = ?`,
57	`0 * ? = ?`,
58
59	`0 / 0 = "division_by_zero"`,
60	`10 / 2 = 5`,
61	`5 / 3 = 5/3`,
62	`5i / 3i = 5/3`,
63	`? / 0 = ?`,
64	`0 / ? = ?`,
65
66	`0 % 0 = "runtime_error:_integer_divide_by_zero"`, // TODO(gri) should be the same as for /
67	`10 % 3 = 1`,
68	`? % 0 = ?`,
69	`0 % ? = ?`,
70
71	`0 & 0 = 0`,
72	`12345 & 0 = 0`,
73	`0xff & 0xf = 0xf`,
74	`? & 0 = ?`,
75	`0 & ? = ?`,
76
77	`0 | 0 = 0`,
78	`12345 | 0 = 12345`,
79	`0xb | 0xa0 = 0xab`,
80	`? | 0 = ?`,
81	`0 | ? = ?`,
82
83	`0 ^ 0 = 0`,
84	`1 ^ -1 = -2`,
85	`? ^ 0 = ?`,
86	`0 ^ ? = ?`,
87
88	`0 &^ 0 = 0`,
89	`0xf &^ 1 = 0xe`,
90	`1 &^ 0xf = 0`,
91	// etc.
92
93	// shifts
94	`0 << 0 = 0`,
95	`1 << 10 = 1024`,
96	`0 >> 0 = 0`,
97	`1024 >> 10 == 1`,
98	`? << 0 == ?`,
99	`? >> 10 == ?`,
100	// etc.
101
102	// comparisons
103	`false == false = true`,
104	`false == true = false`,
105	`true == false = false`,
106	`true == true = true`,
107
108	`false != false = false`,
109	`false != true = true`,
110	`true != false = true`,
111	`true != true = false`,
112
113	`"foo" == "bar" = false`,
114	`"foo" != "bar" = true`,
115	`"foo" < "bar" = false`,
116	`"foo" <= "bar" = false`,
117	`"foo" > "bar" = true`,
118	`"foo" >= "bar" = true`,
119
120	`0 == 0 = true`,
121	`0 != 0 = false`,
122	`0 < 10 = true`,
123	`10 <= 10 = true`,
124	`0 > 10 = false`,
125	`10 >= 10 = true`,
126
127	`1/123456789 == 1/123456789 == true`,
128	`1/123456789 != 1/123456789 == false`,
129	`1/123456789 < 1/123456788 == true`,
130	`1/123456788 <= 1/123456789 == false`,
131	`0.11 > 0.11 = false`,
132	`0.11 >= 0.11 = true`,
133
134	`? == 0 = false`,
135	`? != 0 = false`,
136	`? < 10 = false`,
137	`? <= 10 = false`,
138	`? > 10 = false`,
139	`? >= 10 = false`,
140
141	`0 == ? = false`,
142	`0 != ? = false`,
143	`0 < ? = false`,
144	`10 <= ? = false`,
145	`0 > ? = false`,
146	`10 >= ? = false`,
147
148	// etc.
149}
150
151func TestOps(t *testing.T) {
152	for _, test := range opTests {
153		a := strings.Split(test, " ")
154		i := 0 // operator index
155
156		var x, x0 Value
157		switch len(a) {
158		case 4:
159			// unary operation
160		case 5:
161			// binary operation
162			x, x0 = val(a[0]), val(a[0])
163			i = 1
164		default:
165			t.Errorf("invalid test case: %s", test)
166			continue
167		}
168
169		op, ok := optab[a[i]]
170		if !ok {
171			panic("missing optab entry for " + a[i])
172		}
173
174		y, y0 := val(a[i+1]), val(a[i+1])
175
176		got := doOp(x, op, y)
177		want := val(a[i+3])
178		if !eql(got, want) {
179			t.Errorf("%s: got %s; want %s", test, got, want)
180			continue
181		}
182
183		if x0 != nil && !eql(x, x0) {
184			t.Errorf("%s: x changed to %s", test, x)
185			continue
186		}
187
188		if !eql(y, y0) {
189			t.Errorf("%s: y changed to %s", test, y)
190			continue
191		}
192	}
193}
194
195func eql(x, y Value) bool {
196	_, ux := x.(unknownVal)
197	_, uy := y.(unknownVal)
198	if ux || uy {
199		return ux == uy
200	}
201	return Compare(x, token.EQL, y)
202}
203
204// ----------------------------------------------------------------------------
205// String tests
206
207var xxx = strings.Repeat("x", 68)
208var issue14262 = `"بموجب الشروط التالية نسب المصنف — يجب عليك أن تنسب العمل بالطريقة التي تحددها المؤلف أو المرخص (ولكن ليس بأي حال من الأحوال أن توحي وتقترح بتحول أو استخدامك للعمل).  المشاركة على قدم المساواة — إذا كنت يعدل ، والتغيير ، أو الاستفادة من هذا العمل ، قد ينتج عن توزيع العمل إلا في ظل تشابه او تطابق فى واحد لهذا الترخيص."`
209
210var stringTests = []struct {
211	input, short, exact string
212}{
213	// Unknown
214	{"", "unknown", "unknown"},
215	{"0x", "unknown", "unknown"},
216	{"'", "unknown", "unknown"},
217	{"1f0", "unknown", "unknown"},
218	{"unknown", "unknown", "unknown"},
219
220	// Bool
221	{"true", "true", "true"},
222	{"false", "false", "false"},
223
224	// String
225	{`""`, `""`, `""`},
226	{`"foo"`, `"foo"`, `"foo"`},
227	{`"` + xxx + `xx"`, `"` + xxx + `xx"`, `"` + xxx + `xx"`},
228	{`"` + xxx + `xxx"`, `"` + xxx + `...`, `"` + xxx + `xxx"`},
229	{`"` + xxx + xxx + `xxx"`, `"` + xxx + `...`, `"` + xxx + xxx + `xxx"`},
230	{issue14262, `"بموجب الشروط التالية نسب المصنف — يجب عليك أن تنسب العمل بالطريقة ال...`, issue14262},
231
232	// Int
233	{"0", "0", "0"},
234	{"-1", "-1", "-1"},
235	{"12345", "12345", "12345"},
236	{"-12345678901234567890", "-12345678901234567890", "-12345678901234567890"},
237	{"12345678901234567890", "12345678901234567890", "12345678901234567890"},
238
239	// Float
240	{"0.", "0", "0"},
241	{"-0.0", "0", "0"},
242	{"10.0", "10", "10"},
243	{"2.1", "2.1", "21/10"},
244	{"-2.1", "-2.1", "-21/10"},
245	{"1e9999", "1e+9999", "0x.f8d4a9da224650a8cb2959e10d985ad92adbd44c62917e608b1f24c0e1b76b6f61edffeb15c135a4b601637315f7662f325f82325422b244286a07663c9415d2p+33216"},
246	{"1e-9999", "1e-9999", "0x.83b01ba6d8c0425eec1b21e96f7742d63c2653ed0a024cf8a2f9686df578d7b07d7a83d84df6a2ec70a921d1f6cd5574893a7eda4d28ee719e13a5dce2700759p-33215"},
247	{"2.71828182845904523536028747135266249775724709369995957496696763", "2.71828", "271828182845904523536028747135266249775724709369995957496696763/100000000000000000000000000000000000000000000000000000000000000"},
248	{"0e9999999999", "0", "0"},   // issue #16176
249	{"-6e-1886451601", "0", "0"}, // issue #20228
250
251	// Complex
252	{"0i", "(0 + 0i)", "(0 + 0i)"},
253	{"-0i", "(0 + 0i)", "(0 + 0i)"},
254	{"10i", "(0 + 10i)", "(0 + 10i)"},
255	{"-10i", "(0 + -10i)", "(0 + -10i)"},
256	{"1e9999i", "(0 + 1e+9999i)", "(0 + 0x.f8d4a9da224650a8cb2959e10d985ad92adbd44c62917e608b1f24c0e1b76b6f61edffeb15c135a4b601637315f7662f325f82325422b244286a07663c9415d2p+33216i)"},
257}
258
259func TestString(t *testing.T) {
260	for _, test := range stringTests {
261		x := val(test.input)
262		if got := x.String(); got != test.short {
263			t.Errorf("%s: got %q; want %q as short string", test.input, got, test.short)
264		}
265		if got := x.ExactString(); got != test.exact {
266			t.Errorf("%s: got %q; want %q as exact string", test.input, got, test.exact)
267		}
268	}
269}
270
271// ----------------------------------------------------------------------------
272// Support functions
273
274func val(lit string) Value {
275	if len(lit) == 0 {
276		return MakeUnknown()
277	}
278
279	switch lit {
280	case "?":
281		return MakeUnknown()
282	case "true":
283		return MakeBool(true)
284	case "false":
285		return MakeBool(false)
286	}
287
288	if i := strings.IndexByte(lit, '/'); i >= 0 {
289		// assume fraction
290		a := MakeFromLiteral(lit[:i], token.INT, 0)
291		b := MakeFromLiteral(lit[i+1:], token.INT, 0)
292		return BinaryOp(a, token.QUO, b)
293	}
294
295	tok := token.INT
296	switch first, last := lit[0], lit[len(lit)-1]; {
297	case first == '"' || first == '`':
298		tok = token.STRING
299		lit = strings.Replace(lit, "_", " ", -1)
300	case first == '\'':
301		tok = token.CHAR
302	case last == 'i':
303		tok = token.IMAG
304	default:
305		if !strings.HasPrefix(lit, "0x") && strings.ContainsAny(lit, "./Ee") {
306			tok = token.FLOAT
307		}
308	}
309
310	return MakeFromLiteral(lit, tok, 0)
311}
312
313var optab = map[string]token.Token{
314	"!": token.NOT,
315
316	"+": token.ADD,
317	"-": token.SUB,
318	"*": token.MUL,
319	"/": token.QUO,
320	"%": token.REM,
321
322	"<<": token.SHL,
323	">>": token.SHR,
324
325	"&":  token.AND,
326	"|":  token.OR,
327	"^":  token.XOR,
328	"&^": token.AND_NOT,
329
330	"==": token.EQL,
331	"!=": token.NEQ,
332	"<":  token.LSS,
333	"<=": token.LEQ,
334	">":  token.GTR,
335	">=": token.GEQ,
336}
337
338func panicHandler(v *Value) {
339	switch p := recover().(type) {
340	case nil:
341		// nothing to do
342	case string:
343		*v = MakeString(p)
344	case error:
345		*v = MakeString(p.Error())
346	default:
347		panic(p)
348	}
349}
350
351func doOp(x Value, op token.Token, y Value) (z Value) {
352	defer panicHandler(&z)
353
354	if x == nil {
355		return UnaryOp(op, y, 0)
356	}
357
358	switch op {
359	case token.EQL, token.NEQ, token.LSS, token.LEQ, token.GTR, token.GEQ:
360		return MakeBool(Compare(x, op, y))
361	case token.SHL, token.SHR:
362		s, _ := Int64Val(y)
363		return Shift(x, op, uint(s))
364	default:
365		return BinaryOp(x, op, y)
366	}
367}
368
369// ----------------------------------------------------------------------------
370// Other tests
371
372var fracTests = []string{
373	"0",
374	"1",
375	"-1",
376	"1.2",
377	"-0.991",
378	"2.718281828",
379	"3.14159265358979323e-10",
380	"1e100",
381	"1e1000",
382}
383
384func TestFractions(t *testing.T) {
385	for _, test := range fracTests {
386		x := val(test)
387		// We don't check the actual numerator and denominator because they
388		// are unlikely to be 100% correct due to floatVal rounding errors.
389		// Instead, we compute the fraction again and compare the rounded
390		// result.
391		q := BinaryOp(Num(x), token.QUO, Denom(x))
392		got := q.String()
393		want := x.String()
394		if got != want {
395			t.Errorf("%s: got quotient %s, want %s", x, got, want)
396		}
397	}
398}
399
400var bytesTests = []string{
401	"0",
402	"1",
403	"123456789",
404	"123456789012345678901234567890123456789012345678901234567890",
405}
406
407func TestBytes(t *testing.T) {
408	for _, test := range bytesTests {
409		x := val(test)
410		bytes := Bytes(x)
411
412		// special case 0
413		if Sign(x) == 0 && len(bytes) != 0 {
414			t.Errorf("%s: got %v; want empty byte slice", test, bytes)
415		}
416
417		if n := len(bytes); n > 0 && bytes[n-1] == 0 {
418			t.Errorf("%s: got %v; want no leading 0 byte", test, bytes)
419		}
420
421		if got := MakeFromBytes(bytes); !eql(got, x) {
422			t.Errorf("%s: got %s; want %s (bytes = %v)", test, got, x, bytes)
423		}
424	}
425}
426
427func TestUnknown(t *testing.T) {
428	u := MakeUnknown()
429	var values = []Value{
430		u,
431		MakeBool(false), // token.ADD ok below, operation is never considered
432		MakeString(""),
433		MakeInt64(1),
434		MakeFromLiteral("-1234567890123456789012345678901234567890", token.INT, 0),
435		MakeFloat64(1.2),
436		MakeImag(MakeFloat64(1.2)),
437	}
438	for _, val := range values {
439		x, y := val, u
440		for i := range [2]int{} {
441			if i == 1 {
442				x, y = y, x
443			}
444			if got := BinaryOp(x, token.ADD, y); got.Kind() != Unknown {
445				t.Errorf("%s + %s: got %s; want %s", x, y, got, u)
446			}
447			if got := Compare(x, token.EQL, y); got {
448				t.Errorf("%s == %s: got true; want false", x, y)
449			}
450		}
451	}
452}
453
454func BenchmarkStringAdd(b *testing.B) {
455	for size := 1; size <= 65536; size *= 4 {
456		b.Run(fmt.Sprint(size), func(b *testing.B) {
457			b.ReportAllocs()
458			n := int64(0)
459			for i := 0; i < b.N; i++ {
460				x := MakeString(strings.Repeat("x", 100))
461				y := x
462				for j := 0; j < size-1; j++ {
463					y = BinaryOp(y, token.ADD, x)
464				}
465				n += int64(len(StringVal(y)))
466			}
467			if n != int64(b.N)*int64(size)*100 {
468				b.Fatalf("bad string %d != %d", n, int64(b.N)*int64(size)*100)
469			}
470		})
471	}
472}
473