diff options
Diffstat (limited to 'starlark/testdata')
-rw-r--r-- | starlark/testdata/assign.star | 354 | ||||
-rw-r--r-- | starlark/testdata/benchmark.star | 62 | ||||
-rw-r--r-- | starlark/testdata/bool.star | 62 | ||||
-rw-r--r-- | starlark/testdata/builtins.star | 225 | ||||
-rw-r--r-- | starlark/testdata/bytes.star | 159 | ||||
-rw-r--r-- | starlark/testdata/control.star | 64 | ||||
-rw-r--r-- | starlark/testdata/dict.star | 248 | ||||
-rw-r--r-- | starlark/testdata/float.star | 504 | ||||
-rw-r--r-- | starlark/testdata/function.star | 323 | ||||
-rw-r--r-- | starlark/testdata/int.star | 260 | ||||
-rw-r--r-- | starlark/testdata/json.star | 147 | ||||
-rw-r--r-- | starlark/testdata/list.star | 276 | ||||
-rw-r--r-- | starlark/testdata/misc.star | 139 | ||||
-rw-r--r-- | starlark/testdata/module.star | 17 | ||||
-rw-r--r-- | starlark/testdata/paths.star | 250 | ||||
-rw-r--r-- | starlark/testdata/recursion.star | 43 | ||||
-rw-r--r-- | starlark/testdata/set.star | 118 | ||||
-rw-r--r-- | starlark/testdata/string.star | 472 | ||||
-rw-r--r-- | starlark/testdata/tuple.star | 55 |
19 files changed, 3778 insertions, 0 deletions
diff --git a/starlark/testdata/assign.star b/starlark/testdata/assign.star new file mode 100644 index 0000000..7f579f0 --- /dev/null +++ b/starlark/testdata/assign.star @@ -0,0 +1,354 @@ +# Tests of Starlark assignment. + +# This is a "chunked" file: each "---" effectively starts a new file. + +# tuple assignment +load("assert.star", "assert") + +() = () # empty ok + +a, b, c = 1, 2, 3 +assert.eq(a, 1) +assert.eq(b, 2) +assert.eq(c, 3) + +(d, e, f,) = (1, 2, 3) # trailing comma ok +--- +(a, b, c) = 1 ### "got int in sequence assignment" +--- +(a, b) = () ### "too few values to unpack" +--- +(a, b) = (1,) ### "too few values to unpack" +--- +(a, b, c) = (1, 2) ### "too few values to unpack" +--- +(a, b) = (1, 2, 3) ### "too many values to unpack" +--- +() = 1 ### "got int in sequence assignment" +--- +() = (1,) ### "too many values to unpack" +--- +() = (1, 2) ### "too many values to unpack" +--- +# list assignment +load("assert.star", "assert") + +[] = [] # empty ok + +[a, b, c] = [1, 2, 3] +assert.eq(a, 1) +assert.eq(b, 2) +assert.eq(c, 3) + +[d, e, f,] = [1, 2, 3] # trailing comma ok +--- +[a, b, c] = 1 ### "got int in sequence assignment" +--- +[a, b] = [] ### "too few values to unpack" +--- +[a, b] = [1] ### "too few values to unpack" +--- +[a, b, c] = [1, 2] ### "too few values to unpack" +--- +[a, b] = [1, 2, 3] ### "too many values to unpack" +--- +[] = 1 ### "got int in sequence assignment" +--- +[] = [1] ### "too many values to unpack" +--- +[] = [1, 2] ### "too many values to unpack" +--- +# list-tuple assignment +load("assert.star", "assert") + +# empty ok +[] = () +() = [] + +[a, b, c] = (1, 2, 3) +assert.eq(a, 1) +assert.eq(b, 2) +assert.eq(c, 3) + +[a2, b2, c2] = 1, 2, 3 # bare tuple ok + +(d, e, f) = [1, 2, 3] +assert.eq(d, 1) +assert.eq(e, 2) +assert.eq(f, 3) + +[g, h, (i, j)] = (1, 2, [3, 4]) +assert.eq(g, 1) +assert.eq(h, 2) +assert.eq(i, 3) +assert.eq(j, 4) + +(k, l, [m, n]) = [1, 2, (3, 4)] +assert.eq(k, 1) +assert.eq(l, 2) +assert.eq(m, 3) +assert.eq(n, 4) + +--- +# misc assignment +load("assert.star", "assert") + +def assignment(): + a = [1, 2, 3] + a[1] = 5 + assert.eq(a, [1, 5, 3]) + a[-2] = 2 + assert.eq(a, [1, 2, 3]) + assert.eq("%d %d" % (5, 7), "5 7") + x={} + x[1] = 2 + x[1] += 3 + assert.eq(x[1], 5) + def f12(): x[(1, "abc", {})] = 1 + assert.fails(f12, "unhashable type: dict") + +assignment() + +--- +# augmented assignment + +load("assert.star", "assert") + +def f(): + x = 1 + x += 1 + assert.eq(x, 2) + x *= 3 + assert.eq(x, 6) +f() + +--- +# effects of evaluating LHS occur only once + +load("assert.star", "assert") + +count = [0] # count[0] is the number of calls to f + +def f(): + count[0] += 1 + return count[0] + +x = [1, 2, 3] +x[f()] += 1 + +assert.eq(x, [1, 3, 3]) # sole call to f returned 1 +assert.eq(count[0], 1) # f was called only once + +--- +# Order of evaluation. + +load("assert.star", "assert") + +calls = [] + +def f(name, result): + calls.append(name) + return result + +# The right side is evaluated before the left in an ordinary assignment. +calls.clear() +f("array", [0])[f("index", 0)] = f("rhs", 0) +assert.eq(calls, ["rhs", "array", "index"]) + +calls.clear() +f("lhs1", [0])[0], f("lhs2", [0])[0] = f("rhs1", 0), f("rhs2", 0) +assert.eq(calls, ["rhs1", "rhs2", "lhs1", "lhs2"]) + +# Left side is evaluated first (and only once) in an augmented assignment. +calls.clear() +f("array", [0])[f("index", 0)] += f("addend", 1) +assert.eq(calls, ["array", "index", "addend"]) + +--- +# global referenced before assignment + +def f(): + return g ### "global variable g referenced before assignment" + +f() + +g = 1 + +--- +# Free variables are captured by reference, so this is ok. +load("assert.star", "assert") + +def f(): + def g(): + return outer + outer = 1 + return g() + +assert.eq(f(), 1) + +--- +load("assert.star", "assert") + +printok = [False] + +# This program should resolve successfully but fail dynamically. +# However, the Java implementation currently reports the dynamic +# error at the x=1 statement (b/33975425). I think we need to simplify +# the resolver algorithm to what we have implemented. +def use_before_def(): + print(x) # dynamic error: local var referenced before assignment + printok[0] = True + x = 1 # makes 'x' local + +assert.fails(use_before_def, 'local variable x referenced before assignment') +assert.true(not printok[0]) # execution of print statement failed + +--- +x = [1] +x.extend([2]) # ok + +def f(): + x += [4] ### "local variable x referenced before assignment" + +f() + +--- + +z += 3 ### "global variable z referenced before assignment" + +--- +load("assert.star", "assert") + +# It's ok to define a global that shadows a built-in... +list = [] +assert.eq(type(list), "list") + +# ...but then all uses refer to the global, +# even if they occur before the binding use. +# See github.com/google/skylark/issues/116. +assert.fails(lambda: tuple, "global variable tuple referenced before assignment") +tuple = () + +--- +# option:set +# Same as above, but set is dialect-specific; +# we shouldn't notice any difference. +load("assert.star", "assert") + +set = [1, 2, 3] +assert.eq(type(set), "list") + +# As in Python 2 and Python 3, +# all 'in x' expressions in a comprehension are evaluated +# in the comprehension's lexical block, except the first, +# which is resolved in the outer block. +x = [[1, 2]] +assert.eq([x for x in x for y in x], + [[1, 2], [1, 2]]) + +--- +# A comprehension establishes a single new lexical block, +# not one per 'for' clause. +x = [1, 2] +_ = [x for _ in [3] for x in x] ### "local variable x referenced before assignment" + +--- +load("assert.star", "assert") + +# assign singleton sequence to 1-tuple +(x,) = (1,) +assert.eq(x, 1) +(y,) = [1] +assert.eq(y, 1) + +# assign 1-tuple to variable +z = (1,) +assert.eq(type(z), "tuple") +assert.eq(len(z), 1) +assert.eq(z[0], 1) + +# assign value to parenthesized variable +(a) = 1 +assert.eq(a, 1) + +--- +# assignment to/from fields. +load("assert.star", "assert", "freeze") + +hf = hasfields() +hf.x = 1 +assert.eq(hf.x, 1) +hf.x = [1, 2] +hf.x += [3, 4] +assert.eq(hf.x, [1, 2, 3, 4]) +freeze(hf) +def setX(hf): + hf.x = 2 +def setY(hf): + hf.y = 3 +assert.fails(lambda: setX(hf), "cannot set field on a frozen hasfields") +assert.fails(lambda: setY(hf), "cannot set field on a frozen hasfields") + +--- +# destucturing assignment in a for loop. +load("assert.star", "assert") + +def f(): + res = [] + for (x, y), z in [(["a", "b"], 3), (["c", "d"], 4)]: + res.append((x, y, z)) + return res +assert.eq(f(), [("a", "b", 3), ("c", "d", 4)]) + +def g(): + a = {} + for i, a[i] in [("one", 1), ("two", 2)]: + pass + return a +assert.eq(g(), {"one": 1, "two": 2}) + +--- +# parenthesized LHS in augmented assignment (success) +# option:globalreassign +load("assert.star", "assert") + +a = 5 +(a) += 3 +assert.eq(a, 8) + +--- +# parenthesized LHS in augmented assignment (error) + +(a) += 5 ### "global variable a referenced before assignment" + +--- +# option:globalreassign +load("assert.star", "assert") +assert = 1 +load("assert.star", "assert") + +--- +# option:globalreassign option:loadbindsglobally +load("assert.star", "assert") +assert = 1 +load("assert.star", "assert") + +--- +# option:loadbindsglobally +_ = assert ### "global variable assert referenced before assignment" +load("assert.star", "assert") + +--- +_ = assert ### "local variable assert referenced before assignment" +load("assert.star", "assert") + +--- +def f(): assert.eq(1, 1) # forward ref OK +load("assert.star", "assert") +f() + +--- +# option:loadbindsglobally +def f(): assert.eq(1, 1) # forward ref OK +load("assert.star", "assert") +f() diff --git a/starlark/testdata/benchmark.star b/starlark/testdata/benchmark.star new file mode 100644 index 0000000..b02868d --- /dev/null +++ b/starlark/testdata/benchmark.star @@ -0,0 +1,62 @@ +# Benchmarks of Starlark execution + +def bench_range_construction(b): + for _ in range(b.n): + range(200) + +def bench_range_iteration(b): + for _ in range(b.n): + for x in range(200): + pass + +# Make a 2-level call tree of 100 * 100 calls. +def bench_calling(b): + list = range(100) + + def g(): + for x in list: + pass + + def f(): + for x in list: + g() + + for _ in range(b.n): + f() + +# Measure overhead of calling a trivial built-in method. +emptydict = {} +range1000 = range(1000) + +def bench_builtin_method(b): + for _ in range(b.n): + for _ in range1000: + emptydict.get(None) + +def bench_int(b): + for _ in range(b.n): + a = 0 + for _ in range1000: + a += 1 + +def bench_bigint(b): + for _ in range(b.n): + a = 1 << 31 # maxint32 + 1 + for _ in range1000: + a += 1 + +def bench_gauss(b): + # Sum of arithmetic series. All results fit in int32. + for _ in range(b.n): + acc = 0 + for x in range(92000): + acc += x + +def bench_mix(b): + "Benchmark of a simple mix of computation (for, if, arithmetic, comprehension)." + for _ in range(b.n): + x = 0 + for i in range(50): + if i: + x += 1 + a = [x for x in range(i)] diff --git a/starlark/testdata/bool.star b/starlark/testdata/bool.star new file mode 100644 index 0000000..6c084a3 --- /dev/null +++ b/starlark/testdata/bool.star @@ -0,0 +1,62 @@ +# Tests of Starlark 'bool' + +load("assert.star", "assert") + +# truth +assert.true(True) +assert.true(not False) +assert.true(not not True) +assert.true(not not 1 >= 1) + +# precedence of not +assert.true(not not 2 > 1) +# assert.true(not (not 2) > 1) # TODO(adonovan): fix: gives error for False > 1. +# assert.true(not ((not 2) > 1)) # TODO(adonovan): fix +# assert.true(not ((not (not 2)) > 1)) # TODO(adonovan): fix +# assert.true(not not not (2 > 1)) + +# bool conversion +assert.eq( + [bool(), bool(1), bool(0), bool("hello"), bool("")], + [False, True, False, True, False], +) + +# comparison +assert.true(None == None) +assert.true(None != False) +assert.true(None != True) +assert.eq(1 == 1, True) +assert.eq(1 == 2, False) +assert.true(False == False) +assert.true(True == True) + +# ordered comparison +assert.true(False < True) +assert.true(False <= True) +assert.true(False <= False) +assert.true(True > False) +assert.true(True >= False) +assert.true(True >= True) + +# conditional expression +assert.eq(1 if 3 > 2 else 0, 1) +assert.eq(1 if "foo" else 0, 1) +assert.eq(1 if "" else 0, 0) + +# short-circuit evaluation of 'and' and 'or': +# 'or' yields the first true operand, or the last if all are false. +assert.eq(0 or "" or [] or 0, 0) +assert.eq(0 or "" or [] or 123 or 1 // 0, 123) +assert.fails(lambda : 0 or "" or [] or 0 or 1 // 0, "division by zero") + +# 'and' yields the first false operand, or the last if all are true. +assert.eq(1 and "a" and [1] and 123, 123) +assert.eq(1 and "a" and [1] and 0 and 1 // 0, 0) +assert.fails(lambda : 1 and "a" and [1] and 123 and 1 // 0, "division by zero") + +# Built-ins that want a bool want an actual bool, not a truth value. +# See github.com/bazelbuild/starlark/issues/30 +assert.eq(''.splitlines(True), []) +assert.fails(lambda: ''.splitlines(1), 'got int, want bool') +assert.fails(lambda: ''.splitlines("hello"), 'got string, want bool') +assert.fails(lambda: ''.splitlines(0.0), 'got float, want bool') diff --git a/starlark/testdata/builtins.star b/starlark/testdata/builtins.star new file mode 100644 index 0000000..c6591b8 --- /dev/null +++ b/starlark/testdata/builtins.star @@ -0,0 +1,225 @@ +# Tests of Starlark built-in functions +# option:set + +load("assert.star", "assert") + +# len +assert.eq(len([1, 2, 3]), 3) +assert.eq(len((1, 2, 3)), 3) +assert.eq(len({1: 2}), 1) +assert.fails(lambda: len(1), "int.*has no len") + +# and, or +assert.eq(123 or "foo", 123) +assert.eq(0 or "foo", "foo") +assert.eq(123 and "foo", "foo") +assert.eq(0 and "foo", 0) +none = None +_1 = none and none[0] # rhs is not evaluated +_2 = (not none) or none[0] # rhs is not evaluated + +# any, all +assert.true(all([])) +assert.true(all([1, True, "foo"])) +assert.true(not all([1, True, ""])) +assert.true(not any([])) +assert.true(any([0, False, "foo"])) +assert.true(not any([0, False, ""])) + +# in +assert.true(3 in [1, 2, 3]) +assert.true(4 not in [1, 2, 3]) +assert.true(3 in (1, 2, 3)) +assert.true(4 not in (1, 2, 3)) +assert.fails(lambda: 3 in "foo", "in.*requires string as left operand") +assert.true(123 in {123: ""}) +assert.true(456 not in {123:""}) +assert.true([] not in {123: ""}) + +# sorted +assert.eq(sorted([42, 123, 3]), [3, 42, 123]) +assert.eq(sorted([42, 123, 3], reverse=True), [123, 42, 3]) +assert.eq(sorted(["wiz", "foo", "bar"]), ["bar", "foo", "wiz"]) +assert.eq(sorted(["wiz", "foo", "bar"], reverse=True), ["wiz", "foo", "bar"]) +assert.fails(lambda: sorted([1, 2, None, 3]), "int < NoneType not implemented") +assert.fails(lambda: sorted([1, "one"]), "string < int not implemented") +# custom key function +assert.eq(sorted(["two", "three", "four"], key=len), + ["two", "four", "three"]) +assert.eq(sorted(["two", "three", "four"], key=len, reverse=True), + ["three", "four", "two"]) +assert.fails(lambda: sorted([1, 2, 3], key=None), "got NoneType, want callable") +# sort is stable +pairs = [(4, 0), (3, 1), (4, 2), (2, 3), (3, 4), (1, 5), (2, 6), (3, 7)] +assert.eq(sorted(pairs, key=lambda x: x[0]), + [(1, 5), + (2, 3), (2, 6), + (3, 1), (3, 4), (3, 7), + (4, 0), (4, 2)]) +assert.fails(lambda: sorted(1), 'sorted: for parameter iterable: got int, want iterable') + +# reversed +assert.eq(reversed([1, 144, 81, 16]), [16, 81, 144, 1]) + +# set +assert.contains(set([1, 2, 3]), 1) +assert.true(4 not in set([1, 2, 3])) +assert.eq(len(set([1, 2, 3])), 3) +assert.eq(sorted([x for x in set([1, 2, 3])]), [1, 2, 3]) + +# dict +assert.eq(dict([(1, 2), (3, 4)]), {1: 2, 3: 4}) +assert.eq(dict([(1, 2), (3, 4)], foo="bar"), {1: 2, 3: 4, "foo": "bar"}) +assert.eq(dict({1:2, 3:4}), {1: 2, 3: 4}) +assert.eq(dict({1:2, 3:4}.items()), {1: 2, 3: 4}) + +# range +assert.eq("range", type(range(10))) +assert.eq("range(10)", str(range(0, 10, 1))) +assert.eq("range(1, 10)", str(range(1, 10))) +assert.eq(range(0, 5, 10), range(0, 5, 11)) +assert.eq("range(0, 10, -1)", str(range(0, 10, -1))) +assert.fails(lambda: {range(10): 10}, "unhashable: range") +assert.true(bool(range(1, 2))) +assert.true(not(range(2, 1))) # an empty range is false +assert.eq([x*x for x in range(5)], [0, 1, 4, 9, 16]) +assert.eq(list(range(5)), [0, 1, 2, 3, 4]) +assert.eq(list(range(-5)), []) +assert.eq(list(range(2, 5)), [2, 3, 4]) +assert.eq(list(range(5, 2)), []) +assert.eq(list(range(-2, -5)), []) +assert.eq(list(range(-5, -2)), [-5, -4, -3]) +assert.eq(list(range(2, 10, 3)), [2, 5, 8]) +assert.eq(list(range(10, 2, -3)), [10, 7, 4]) +assert.eq(list(range(-2, -10, -3)), [-2, -5, -8]) +assert.eq(list(range(-10, -2, 3)), [-10, -7, -4]) +assert.eq(list(range(10, 2, -1)), [10, 9, 8, 7, 6, 5, 4, 3]) +assert.eq(list(range(5)[1:]), [1, 2, 3, 4]) +assert.eq(len(range(5)[1:]), 4) +assert.eq(list(range(5)[:2]), [0, 1]) +assert.eq(list(range(10)[1:]), [1, 2, 3, 4, 5, 6, 7, 8, 9]) +assert.eq(list(range(10)[1:9:2]), [1, 3, 5, 7]) +assert.eq(list(range(10)[1:10:2]), [1, 3, 5, 7, 9]) +assert.eq(list(range(10)[1:11:2]), [1, 3, 5, 7, 9]) +assert.eq(list(range(10)[::-2]), [9, 7, 5, 3, 1]) +assert.eq(list(range(0, 10, 2)[::2]), [0, 4, 8]) +assert.eq(list(range(0, 10, 2)[::-2]), [8, 4, 0]) +# range() is limited by the width of the Go int type (int32 or int64). +assert.fails(lambda: range(1<<64), "... out of range .want value in signed ..-bit range") +assert.eq(len(range(0x7fffffff)), 0x7fffffff) # O(1) +# Two ranges compare equal if they denote the same sequence: +assert.eq(range(0), range(2, 1, 3)) # [] +assert.eq(range(0, 3, 2), range(0, 4, 2)) # [0, 2] +assert.ne(range(1, 10), range(2, 10)) +assert.fails(lambda: range(0) < range(0), "range < range not implemented") +# <number> in <range> +assert.contains(range(3), 1) +assert.contains(range(3), 2.0) # acts like 2 +assert.fails(lambda: True in range(3), "requires integer.*not bool") # bools aren't numbers +assert.fails(lambda: "one" in range(10), "requires integer.*not string") +assert.true(4 not in range(4)) +assert.true(1e15 not in range(4)) # too big for int32 +assert.true(1e100 not in range(4)) # too big for int64 +# https://github.com/google/starlark-go/issues/116 +assert.fails(lambda: range(0, 0, 2)[:][0], "index 0 out of range: empty range") + +# list +assert.eq(list("abc".elems()), ["a", "b", "c"]) +assert.eq(sorted(list({"a": 1, "b": 2})), ['a', 'b']) + +# min, max +assert.eq(min(5, -2, 1, 7, 3), -2) +assert.eq(max(5, -2, 1, 7, 3), 7) +assert.eq(min([5, -2, 1, 7, 3]), -2) +assert.eq(min("one", "two", "three", "four"), "four") +assert.eq(max("one", "two", "three", "four"), "two") +assert.fails(min, "min requires at least one positional argument") +assert.fails(lambda: min(1), "not iterable") +assert.fails(lambda: min([]), "empty") +assert.eq(min(5, -2, 1, 7, 3, key=lambda x: x*x), 1) # min absolute value +assert.eq(min(5, -2, 1, 7, 3, key=lambda x: -x), 7) # min negated value + +# enumerate +assert.eq(enumerate("abc".elems()), [(0, "a"), (1, "b"), (2, "c")]) +assert.eq(enumerate([False, True, None], 42), [(42, False), (43, True), (44, None)]) + +# zip +assert.eq(zip(), []) +assert.eq(zip([]), []) +assert.eq(zip([1, 2, 3]), [(1,), (2,), (3,)]) +assert.eq(zip("".elems()), []) +assert.eq(zip("abc".elems(), + list("def".elems()), + "hijk".elems()), + [("a", "d", "h"), ("b", "e", "i"), ("c", "f", "j")]) +z1 = [1] +assert.eq(zip(z1), [(1,)]) +z1.append(2) +assert.eq(zip(z1), [(1,), (2,)]) +assert.fails(lambda: zip(z1, 1), "zip: argument #2 is not iterable: int") +z1.append(3) + +# dir for builtin_function_or_method +assert.eq(dir(None), []) +assert.eq(dir({})[:3], ["clear", "get", "items"]) # etc +assert.eq(dir(1), []) +assert.eq(dir([])[:3], ["append", "clear", "extend"]) # etc + +# hasattr, getattr, dir +# hasfields is an application-defined type defined in eval_test.go. +hf = hasfields() +assert.eq(dir(hf), []) +assert.true(not hasattr(hf, "x")) +assert.fails(lambda: getattr(hf, "x"), "no .x field or method") +assert.eq(getattr(hf, "x", 42), 42) +hf.x = 1 +assert.true(hasattr(hf, "x")) +assert.eq(getattr(hf, "x"), 1) +assert.eq(hf.x, 1) +hf.x = 2 +assert.eq(getattr(hf, "x"), 2) +assert.eq(hf.x, 2) +# built-in types can have attributes (methods) too. +myset = set([]) +assert.eq(dir(myset), ["union"]) +assert.true(hasattr(myset, "union")) +assert.true(not hasattr(myset, "onion")) +assert.eq(str(getattr(myset, "union")), "<built-in method union of set value>") +assert.fails(lambda: getattr(myset, "onion"), "no .onion field or method") +assert.eq(getattr(myset, "onion", 42), 42) + +# dir returns a new, sorted, mutable list +assert.eq(sorted(dir("")), dir("")) # sorted +dir("").append("!") # mutable +assert.true("!" not in dir("")) # new + +# error messages should suggest spelling corrections +hf.one = 1 +hf.two = 2 +hf.three = 3 +hf.forty_five = 45 +assert.fails(lambda: hf.One, 'no .One field.*did you mean .one') +assert.fails(lambda: hf.oone, 'no .oone field.*did you mean .one') +assert.fails(lambda: hf.FortyFive, 'no .FortyFive field.*did you mean .forty_five') +assert.fails(lambda: hf.trhee, 'no .trhee field.*did you mean .three') +assert.fails(lambda: hf.thirty, 'no .thirty field or method$') # no suggestion + +# spell check in setfield too +def setfield(): hf.noForty_Five = 46 # "no" prefix => SetField returns NoSuchField +assert.fails(setfield, 'no .noForty_Five field.*did you mean .forty_five') + +# repr +assert.eq(repr(1), "1") +assert.eq(repr("x"), '"x"') +assert.eq(repr(["x", 1]), '["x", 1]') + +# fail +--- +fail() ### `fail: $` +x = 1//0 # unreachable +--- +fail(1) ### `fail: 1` +--- +fail(1, 2, 3) ### `fail: 1 2 3` +--- +fail(1, 2, 3, sep="/") ### `fail: 1/2/3` diff --git a/starlark/testdata/bytes.star b/starlark/testdata/bytes.star new file mode 100644 index 0000000..d500403 --- /dev/null +++ b/starlark/testdata/bytes.star @@ -0,0 +1,159 @@ +# Tests of 'bytes' (immutable byte strings). + +load("assert.star", "assert") + +# bytes(string) -- UTF-k to UTF-8 transcoding with U+FFFD replacement +hello = bytes("hello, 世界") +goodbye = bytes("goodbye") +empty = bytes("") +nonprinting = bytes("\t\n\x7F\u200D") # TAB, NEWLINE, DEL, ZERO_WIDTH_JOINER +assert.eq(bytes("hello, 世界"[:-1]), b"hello, 世��") + +# bytes(iterable of int) -- construct from numeric byte values +assert.eq(bytes([65, 66, 67]), b"ABC") +assert.eq(bytes((65, 66, 67)), b"ABC") +assert.eq(bytes([0xf0, 0x9f, 0x98, 0xbf]), b"😿") +assert.fails(lambda: bytes([300]), + "at index 0, 300 out of range .want value in unsigned 8-bit range") +assert.fails(lambda: bytes([b"a"]), + "at index 0, got bytes, want int") +assert.fails(lambda: bytes(1), "want string, bytes, or iterable of ints") + +# literals +assert.eq(b"hello, 世界", hello) +assert.eq(b"goodbye", goodbye) +assert.eq(b"", empty) +assert.eq(b"\t\n\x7F\u200D", nonprinting) +assert.ne("abc", b"abc") +assert.eq(b"\012\xff\u0400\U0001F63F", b"\n\xffЀ😿") # see scanner tests for more +assert.eq(rb"\r\n\t", b"\\r\\n\\t") # raw + +# type +assert.eq(type(hello), "bytes") + +# len +assert.eq(len(hello), 13) +assert.eq(len(goodbye), 7) +assert.eq(len(empty), 0) +assert.eq(len(b"A"), 1) +assert.eq(len(b"Ѐ"), 2) +assert.eq(len(b"世"), 3) +assert.eq(len(b"😿"), 4) + +# truth +assert.true(hello) +assert.true(goodbye) +assert.true(not empty) + +# str(bytes) does UTF-8 to UTF-k transcoding. +# TODO(adonovan): specify. +assert.eq(str(hello), "hello, 世界") +assert.eq(str(hello[:-1]), "hello, 世��") # incomplete UTF-8 encoding => U+FFFD +assert.eq(str(goodbye), "goodbye") +assert.eq(str(empty), "") +assert.eq(str(nonprinting), "\t\n\x7f\u200d") +assert.eq(str(b"\xED\xB0\x80"), "���") # UTF-8 encoding of unpaired surrogate => U+FFFD x 3 + +# repr +assert.eq(repr(hello), r'b"hello, 世界"') +assert.eq(repr(hello[:-1]), r'b"hello, 世\xe7\x95"') # (incomplete UTF-8 encoding ) +assert.eq(repr(goodbye), 'b"goodbye"') +assert.eq(repr(empty), 'b""') +assert.eq(repr(nonprinting), 'b"\\t\\n\\x7f\\u200d"') + +# equality +assert.eq(hello, hello) +assert.ne(hello, goodbye) +assert.eq(b"goodbye", goodbye) + +# ordered comparison +assert.lt(b"abc", b"abd") +assert.lt(b"abc", b"abcd") +assert.lt(b"\x7f", b"\x80") # bytes compare as uint8, not int8 + +# bytes are dict-hashable +dict = {hello: 1, goodbye: 2} +dict[b"goodbye"] = 3 +assert.eq(len(dict), 2) +assert.eq(dict[goodbye], 3) + +# hash(bytes) is 32-bit FNV-1a. +assert.eq(hash(b""), 0x811c9dc5) +assert.eq(hash(b"a"), 0xe40c292c) +assert.eq(hash(b"ab"), 0x4d2505ca) +assert.eq(hash(b"abc"), 0x1a47e90b) + +# indexing +assert.eq(goodbye[0], b"g") +assert.eq(goodbye[-1], b"e") +assert.fails(lambda: goodbye[100], "out of range") + +# slicing +assert.eq(goodbye[:4], b"good") +assert.eq(goodbye[4:], b"bye") +assert.eq(goodbye[::2], b"gobe") +assert.eq(goodbye[3:4], b"d") # special case: len=1 +assert.eq(goodbye[4:4], b"") # special case: len=0 + +# bytes in bytes +assert.eq(b"bc" in b"abcd", True) +assert.eq(b"bc" in b"dcab", False) +assert.fails(lambda: "bc" in b"dcab", "requires bytes or int as left operand, not string") + +# int in bytes +assert.eq(97 in b"abc", True) # 97='a' +assert.eq(100 in b"abc", False) # 100='d' +assert.fails(lambda: 256 in b"abc", "int in bytes: 256 out of range") +assert.fails(lambda: -1 in b"abc", "int in bytes: -1 out of range") + +# ord TODO(adonovan): specify +assert.eq(ord(b"a"), 97) +assert.fails(lambda: ord(b"ab"), "ord: bytes has length 2, want 1") +assert.fails(lambda: ord(b""), "ord: bytes has length 0, want 1") + +# repeat (bytes * int) +assert.eq(goodbye * 3, b"goodbyegoodbyegoodbye") +assert.eq(3 * goodbye, b"goodbyegoodbyegoodbye") + +# elems() returns an iterable value over 1-byte substrings. +assert.eq(type(hello.elems()), "bytes.elems") +assert.eq(str(hello.elems()), "b\"hello, 世界\".elems()") +assert.eq(list(hello.elems()), [104, 101, 108, 108, 111, 44, 32, 228, 184, 150, 231, 149, 140]) +assert.eq(bytes([104, 101, 108, 108, 111, 44, 32, 228, 184, 150, 231, 149, 140]), hello) +assert.eq(list(goodbye.elems()), [103, 111, 111, 100, 98, 121, 101]) +assert.eq(list(empty.elems()), []) +assert.eq(bytes(hello.elems()), hello) # bytes(iterable) is dual to bytes.elems() + +# x[i] = ... +def f(): + b"abc"[1] = b"B" + +assert.fails(f, "bytes.*does not support.*assignment") + +# TODO(adonovan): the specification is not finalized in many areas: +# - chr, ord functions +# - encoding/decoding bytes to string. +# - methods: find, index, split, etc. +# +# Summary of string operations (put this in spec). +# +# string to number: +# - bytes[i] returns numeric value of ith byte. +# - ord(string) returns numeric value of sole code point in string. +# - ord(string[i]) is not a useful operation: fails on non-ASCII; see below. +# Q. Perhaps ord should return the first (not sole) code point? Then it becomes a UTF-8 decoder. +# Perhaps ord(string, index=int) should apply the index and relax the len=1 check. +# - string.codepoint() iterates over 1-codepoint substrings. +# - string.codepoint_ords() iterates over numeric values of code points in string. +# - string.elems() iterates over 1-element (UTF-k code) substrings. +# - string.elem_ords() iterates over numeric UTF-k code values. +# - string.elem_ords()[i] returns numeric value of ith element (UTF-k code). +# - string.elems()[i] returns substring of a single element (UTF-k code). +# - int(string) parses string as decimal (or other) numeric literal. +# +# number to string: +# - chr(int) returns string, UTF-k encoding of Unicode code point (like Python). +# Redundant with '%c' % int (which Python2 calls 'unichr'.) +# - bytes(chr(int)) returns byte string containing UTF-8 encoding of one code point. +# - bytes([int]) returns 1-byte string (with regrettable list allocation). +# - str(int) - format number as decimal. diff --git a/starlark/testdata/control.star b/starlark/testdata/control.star new file mode 100644 index 0000000..554ab25 --- /dev/null +++ b/starlark/testdata/control.star @@ -0,0 +1,64 @@ +# Tests of Starlark control flow + +load("assert.star", "assert") + +def controlflow(): + # elif + x = 0 + if True: + x=1 + elif False: + assert.fail("else of true") + else: + assert.fail("else of else of true") + assert.true(x) + + x = 0 + if False: + assert.fail("then of false") + elif True: + x = 1 + else: + assert.fail("else of true") + assert.true(x) + + x = 0 + if False: + assert.fail("then of false") + elif False: + assert.fail("then of false") + else: + x = 1 + assert.true(x) +controlflow() + +def loops(): + y = "" + for x in [1, 2, 3, 4, 5]: + if x == 2: + continue + if x == 4: + break + y = y + str(x) + return y +assert.eq(loops(), "13") + +# return +g = 123 +def f(x): + for g in (1, 2, 3): + if g == x: + return g +assert.eq(f(2), 2) +assert.eq(f(4), None) # falling off end => return None +assert.eq(g, 123) # unchanged by local use of g in function + +# infinite sequences +def fib(n): + seq = [] + for x in fibonacci: # fibonacci is an infinite iterable defined in eval_test.go + if len(seq) == n: + break + seq.append(x) + return seq +assert.eq(fib(10), [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]) diff --git a/starlark/testdata/dict.star b/starlark/testdata/dict.star new file mode 100644 index 0000000..1aeb1e7 --- /dev/null +++ b/starlark/testdata/dict.star @@ -0,0 +1,248 @@ +# Tests of Starlark 'dict' + +load("assert.star", "assert", "freeze") + +# literals +assert.eq({}, {}) +assert.eq({"a": 1}, {"a": 1}) +assert.eq({"a": 1,}, {"a": 1}) + +# truth +assert.true({False: False}) +assert.true(not {}) + +# dict + dict is no longer supported. +assert.fails(lambda: {"a": 1} + {"b": 2}, 'unknown binary op: dict \\+ dict') + +# dict comprehension +assert.eq({x: x*x for x in range(3)}, {0: 0, 1: 1, 2: 4}) + +# dict.pop +x6 = {"a": 1, "b": 2} +assert.eq(x6.pop("a"), 1) +assert.eq(str(x6), '{"b": 2}') +assert.fails(lambda: x6.pop("c"), "pop: missing key") +assert.eq(x6.pop("c", 3), 3) +assert.eq(x6.pop("c", None), None) # default=None tests an edge case of UnpackArgs +assert.eq(x6.pop("b"), 2) +assert.eq(len(x6), 0) + +# dict.popitem +x7 = {"a": 1, "b": 2} +assert.eq([x7.popitem(), x7.popitem()], [("a", 1), ("b", 2)]) +assert.fails(x7.popitem, "empty dict") +assert.eq(len(x7), 0) + +# dict.keys, dict.values +x8 = {"a": 1, "b": 2} +assert.eq(x8.keys(), ["a", "b"]) +assert.eq(x8.values(), [1, 2]) + +# equality +assert.eq({"a": 1, "b": 2}, {"a": 1, "b": 2}) +assert.eq({"a": 1, "b": 2,}, {"a": 1, "b": 2}) +assert.eq({"a": 1, "b": 2}, {"b": 2, "a": 1}) + +# insertion order is preserved +assert.eq(dict([("a", 0), ("b", 1), ("c", 2), ("b", 3)]).keys(), ["a", "b", "c"]) +assert.eq(dict([("b", 0), ("a", 1), ("b", 2), ("c", 3)]).keys(), ["b", "a", "c"]) +assert.eq(dict([("b", 0), ("a", 1), ("b", 2), ("c", 3)])["b"], 2) +# ...even after rehashing (which currently occurs after key 'i'): +small = dict([("a", 0), ("b", 1), ("c", 2)]) +small.update([("d", 4), ("e", 5), ("f", 6), ("g", 7), ("h", 8), ("i", 9), ("j", 10), ("k", 11)]) +assert.eq(small.keys(), ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"]) + +# Duplicate keys are not permitted in dictionary expressions (see b/35698444). +# (Nor in keyword args to function calls---checked by resolver.) +assert.fails(lambda: {"aa": 1, "bb": 2, "cc": 3, "bb": 4}, 'duplicate key: "bb"') + +# Check that even with many positional args, keyword collisions are detected. +assert.fails(lambda: dict({'b': 3}, a=4, **dict(a=5)), 'dict: duplicate keyword arg: "a"') +assert.fails(lambda: dict({'a': 2, 'b': 3}, a=4, **dict(a=5)), 'dict: duplicate keyword arg: "a"') +# positional/keyword arg key collisions are ok +assert.eq(dict((['a', 2], ), a=4), {'a': 4}) +assert.eq(dict((['a', 2], ['a', 3]), a=4), {'a': 4}) + +# index +def setIndex(d, k, v): + d[k] = v + +x9 = {} +assert.fails(lambda: x9["a"], 'key "a" not in dict') +x9["a"] = 1 +assert.eq(x9["a"], 1) +assert.eq(x9, {"a": 1}) +assert.fails(lambda: setIndex(x9, [], 2), 'unhashable type: list') +freeze(x9) +assert.fails(lambda: setIndex(x9, "a", 3), 'cannot insert into frozen hash table') + +x9a = {} +x9a[1, 2] = 3 # unparenthesized tuple is allowed here +assert.eq(x9a.keys()[0], (1, 2)) + +# dict.get +x10 = {"a": 1} +assert.eq(x10.get("a"), 1) +assert.eq(x10.get("b"), None) +assert.eq(x10.get("a", 2), 1) +assert.eq(x10.get("b", 2), 2) + +# dict.clear +x11 = {"a": 1} +assert.contains(x11, "a") +assert.eq(x11["a"], 1) +x11.clear() +assert.fails(lambda: x11["a"], 'key "a" not in dict') +assert.true("a" not in x11) +freeze(x11) +assert.fails(x11.clear, "cannot clear frozen hash table") + +# dict.setdefault +x12 = {"a": 1} +assert.eq(x12.setdefault("a"), 1) +assert.eq(x12["a"], 1) +assert.eq(x12.setdefault("b"), None) +assert.eq(x12["b"], None) +assert.eq(x12.setdefault("c", 2), 2) +assert.eq(x12["c"], 2) +assert.eq(x12.setdefault("c", 3), 2) +assert.eq(x12["c"], 2) +freeze(x12) +assert.eq(x12.setdefault("a", 1), 1) # no change, no error +assert.fails(lambda: x12.setdefault("d", 1), "cannot insert into frozen hash table") + +# dict.update +x13 = {"a": 1} +x13.update(a=2, b=3) +assert.eq(x13, {"a": 2, "b": 3}) +x13.update([("b", 4), ("c", 5)]) +assert.eq(x13, {"a": 2, "b": 4, "c": 5}) +x13.update({"c": 6, "d": 7}) +assert.eq(x13, {"a": 2, "b": 4, "c": 6, "d": 7}) +freeze(x13) +assert.fails(lambda: x13.update({"a": 8}), "cannot insert into frozen hash table") + +# dict as a sequence +# +# for loop +x14 = {1:2, 3:4} +def keys(dict): + keys = [] + for k in dict: keys.append(k) + return keys +assert.eq(keys(x14), [1, 3]) +# +# comprehension +assert.eq([x for x in x14], [1, 3]) +# +# varargs +def varargs(*args): return args +x15 = {"one": 1} +assert.eq(varargs(*x15), ("one",)) + +# kwargs parameter does not alias the **kwargs dict +def kwargs(**kwargs): return kwargs +x16 = kwargs(**x15) +assert.eq(x16, x15) +x15["two"] = 2 # mutate +assert.ne(x16, x15) + +# iterator invalidation +def iterator1(): + dict = {1:1, 2:1} + for k in dict: + dict[2*k] = dict[k] +assert.fails(iterator1, "insert.*during iteration") + +def iterator2(): + dict = {1:1, 2:1} + for k in dict: + dict.pop(k) +assert.fails(iterator2, "delete.*during iteration") + +def iterator3(): + def f(d): + d[3] = 3 + dict = {1:1, 2:1} + _ = [f(dict) for x in dict] +assert.fails(iterator3, "insert.*during iteration") + +# This assignment is not a modification-during-iteration: +# the sequence x should be completely iterated before +# the assignment occurs. +def f(): + x = {1:2, 2:4} + a, x[0] = x + assert.eq(a, 1) + assert.eq(x, {1: 2, 2: 4, 0: 2}) +f() + +# Regression test for a bug in hashtable.delete +def test_delete(): + d = {} + + # delete tail first + d["one"] = 1 + d["two"] = 2 + assert.eq(str(d), '{"one": 1, "two": 2}') + d.pop("two") + assert.eq(str(d), '{"one": 1}') + d.pop("one") + assert.eq(str(d), '{}') + + # delete head first + d["one"] = 1 + d["two"] = 2 + assert.eq(str(d), '{"one": 1, "two": 2}') + d.pop("one") + assert.eq(str(d), '{"two": 2}') + d.pop("two") + assert.eq(str(d), '{}') + + # delete middle + d["one"] = 1 + d["two"] = 2 + d["three"] = 3 + assert.eq(str(d), '{"one": 1, "two": 2, "three": 3}') + d.pop("two") + assert.eq(str(d), '{"one": 1, "three": 3}') + d.pop("three") + assert.eq(str(d), '{"one": 1}') + d.pop("one") + assert.eq(str(d), '{}') + +test_delete() + +# Regression test for github.com/google/starlark-go/issues/128. +assert.fails(lambda: dict(None), 'got NoneType, want iterable') +assert.fails(lambda: {}.update(None), 'got NoneType, want iterable') + +--- +# Verify position of an "unhashable key" error in a dict literal. + +_ = { + "one": 1, + ["two"]: 2, ### "unhashable type: list" + "three": 3, +} + +--- +# Verify position of a "duplicate key" error in a dict literal. + +_ = { + "one": 1, + "one": 1, ### `duplicate key: "one"` + "three": 3, +} + +--- +# Verify position of an "unhashable key" error in a dict comprehension. + +_ = { + k: v ### "unhashable type: list" + for k, v in [ + ("one", 1), + (["two"], 2), + ("three", 3), + ] +} diff --git a/starlark/testdata/float.star b/starlark/testdata/float.star new file mode 100644 index 0000000..b4df38d --- /dev/null +++ b/starlark/testdata/float.star @@ -0,0 +1,504 @@ +# Tests of Starlark 'float' +# option:set + +load("assert.star", "assert") + +# TODO(adonovan): more tests: +# - precision +# - limits + +# type +assert.eq(type(0.0), "float") + +# truth +assert.true(123.0) +assert.true(-1.0) +assert.true(not 0.0) +assert.true(-1.0e-45) +assert.true(float("NaN")) + +# not iterable +assert.fails(lambda: len(0.0), 'has no len') +assert.fails(lambda: [x for x in 0.0], 'float value is not iterable') + +# literals +assert.eq(type(1.234), "float") +assert.eq(type(1e10), "float") +assert.eq(type(1e+10), "float") +assert.eq(type(1e-10), "float") +assert.eq(type(1.234e10), "float") +assert.eq(type(1.234e+10), "float") +assert.eq(type(1.234e-10), "float") + +# int/float equality +assert.eq(0.0, 0) +assert.eq(0, 0.0) +assert.eq(1.0, 1) +assert.eq(1, 1.0) +assert.true(1.23e45 != 1229999999999999973814869011019624571608236031) +assert.true(1.23e45 == 1229999999999999973814869011019624571608236032) +assert.true(1.23e45 != 1229999999999999973814869011019624571608236033) +assert.true(1229999999999999973814869011019624571608236031 != 1.23e45) +assert.true(1229999999999999973814869011019624571608236032 == 1.23e45) +assert.true(1229999999999999973814869011019624571608236033 != 1.23e45) + +# loss of precision +p53 = 1<<53 +assert.eq(float(p53-1), p53-1) +assert.eq(float(p53+0), p53+0) +assert.eq(float(p53+1), p53+0) # +assert.eq(float(p53+2), p53+2) +assert.eq(float(p53+3), p53+4) # +assert.eq(float(p53+4), p53+4) +assert.eq(float(p53+5), p53+4) # +assert.eq(float(p53+6), p53+6) +assert.eq(float(p53+7), p53+8) # +assert.eq(float(p53+8), p53+8) + +assert.true(float(p53+1) != p53+1) # comparisons are exact +assert.eq(float(p53+1) - (p53+1), 0) # arithmetic entails rounding + +assert.fails(lambda: {123.0: "f", 123: "i"}, "duplicate key: 123") + +# equal int/float values have same hash +d = {123.0: "x"} +d[123] = "y" +assert.eq(len(d), 1) +assert.eq(d[123.0], "y") + +# literals (mostly covered by scanner tests) +assert.eq(str(0.), "0.0") +assert.eq(str(.0), "0.0") +assert.true(5.0 != 4.999999999999999) +assert.eq(5.0, 4.9999999999999999) # both literals denote 5.0 +assert.eq(1.23e45, 1.23 * 1000000000000000000000000000000000000000000000) +assert.eq(str(1.23e-45 - (1.23 / 1000000000000000000000000000000000000000000000)), "-1.5557538194652854e-61") + +nan = float("NaN") +inf = float("+Inf") +neginf = float("-Inf") +negzero = (-1e-323 / 10) + +# -- arithmetic -- + +# +float, -float +assert.eq(+(123.0), 123.0) +assert.eq(-(123.0), -123.0) +assert.eq(-(-(123.0)), 123.0) +assert.eq(+(inf), inf) +assert.eq(-(inf), neginf) +assert.eq(-(neginf), inf) +assert.eq(str(-(nan)), "nan") +# + +assert.eq(1.2e3 + 5.6e7, 5.60012e+07) +assert.eq(1.2e3 + 1, 1201) +assert.eq(1 + 1.2e3, 1201) +assert.eq(str(1.2e3 + nan), "nan") +assert.eq(inf + 0, inf) +assert.eq(inf + 1, inf) +assert.eq(inf + inf, inf) +assert.eq(str(inf + neginf), "nan") +# - +assert.eq(1.2e3 - 5.6e7, -5.59988e+07) +assert.eq(1.2e3 - 1, 1199) +assert.eq(1 - 1.2e3, -1199) +assert.eq(str(1.2e3 - nan), "nan") +assert.eq(inf - 0, inf) +assert.eq(inf - 1, inf) +assert.eq(str(inf - inf), "nan") +assert.eq(inf - neginf, inf) +# * +assert.eq(1.5e6 * 2.2e3, 3.3e9) +assert.eq(1.5e6 * 123, 1.845e+08) +assert.eq(123 * 1.5e6, 1.845e+08) +assert.eq(str(1.2e3 * nan), "nan") +assert.eq(str(inf * 0), "nan") +assert.eq(inf * 1, inf) +assert.eq(inf * inf, inf) +assert.eq(inf * neginf, neginf) +# % +assert.eq(100.0 % 7.0, 2) +assert.eq(100.0 % -7.0, -5) # NB: different from Go / Java +assert.eq(-100.0 % 7.0, 5) # NB: different from Go / Java +assert.eq(-100.0 % -7.0, -2) +assert.eq(-100.0 % 7, 5) +assert.eq(100 % 7.0, 2) +assert.eq(str(1.2e3 % nan), "nan") +assert.eq(str(inf % 1), "nan") +assert.eq(str(inf % inf), "nan") +assert.eq(str(inf % neginf), "nan") +# / +assert.eq(str(100.0 / 7.0), "14.285714285714286") +assert.eq(str(100 / 7.0), "14.285714285714286") +assert.eq(str(100.0 / 7), "14.285714285714286") +assert.eq(str(100.0 / nan), "nan") +# // +assert.eq(100.0 // 7.0, 14) +assert.eq(100 // 7.0, 14) +assert.eq(100.0 // 7, 14) +assert.eq(100.0 // -7.0, -15) +assert.eq(100 // -7.0, -15) +assert.eq(100.0 // -7, -15) +assert.eq(str(1 // neginf), "-0.0") +assert.eq(str(100.0 // nan), "nan") + +# addition +assert.eq(0.0 + 1.0, 1.0) +assert.eq(1.0 + 1.0, 2.0) +assert.eq(1.25 + 2.75, 4.0) +assert.eq(5.0 + 7.0, 12.0) +assert.eq(5.1 + 7, 12.1) # float + int +assert.eq(7 + 5.1, 12.1) # int + float + +# subtraction +assert.eq(5.0 - 7.0, -2.0) +assert.eq(5.1 - 7.1, -2.0) +assert.eq(5.5 - 7, -1.5) +assert.eq(5 - 7.5, -2.5) +assert.eq(0.0 - 1.0, -1.0) + +# multiplication +assert.eq(5.0 * 7.0, 35.0) +assert.eq(5.5 * 2.5, 13.75) +assert.eq(5.5 * 7, 38.5) +assert.eq(5 * 7.1, 35.5) + +# real division (like Python 3) +# The / operator is available only when the 'fp' dialect option is enabled. +assert.eq(100.0 / 8.0, 12.5) +assert.eq(100.0 / -8.0, -12.5) +assert.eq(-100.0 / 8.0, -12.5) +assert.eq(-100.0 / -8.0, 12.5) +assert.eq(98.0 / 8.0, 12.25) +assert.eq(98.0 / -8.0, -12.25) +assert.eq(-98.0 / 8.0, -12.25) +assert.eq(-98.0 / -8.0, 12.25) +assert.eq(2.5 / 2.0, 1.25) +assert.eq(2.5 / 2, 1.25) +assert.eq(5 / 4.0, 1.25) +assert.eq(5 / 4, 1.25) +assert.fails(lambda: 1.0 / 0, "floating-point division by zero") +assert.fails(lambda: 1.0 / 0.0, "floating-point division by zero") +assert.fails(lambda: 1 / 0.0, "floating-point division by zero") + +# floored division +assert.eq(100.0 // 8.0, 12.0) +assert.eq(100.0 // -8.0, -13.0) +assert.eq(-100.0 // 8.0, -13.0) +assert.eq(-100.0 // -8.0, 12.0) +assert.eq(98.0 // 8.0, 12.0) +assert.eq(98.0 // -8.0, -13.0) +assert.eq(-98.0 // 8.0, -13.0) +assert.eq(-98.0 // -8.0, 12.0) +assert.eq(2.5 // 2.0, 1.0) +assert.eq(2.5 // 2, 1.0) +assert.eq(5 // 4.0, 1.0) +assert.eq(5 // 4, 1) +assert.eq(type(5 // 4), "int") +assert.fails(lambda: 1.0 // 0, "floored division by zero") +assert.fails(lambda: 1.0 // 0.0, "floored division by zero") +assert.fails(lambda: 1 // 0.0, "floored division by zero") + +# remainder +assert.eq(100.0 % 8.0, 4.0) +assert.eq(100.0 % -8.0, -4.0) +assert.eq(-100.0 % 8.0, 4.0) +assert.eq(-100.0 % -8.0, -4.0) +assert.eq(98.0 % 8.0, 2.0) +assert.eq(98.0 % -8.0, -6.0) +assert.eq(-98.0 % 8.0, 6.0) +assert.eq(-98.0 % -8.0, -2.0) +assert.eq(2.5 % 2.0, 0.5) +assert.eq(2.5 % 2, 0.5) +assert.eq(5 % 4.0, 1.0) +assert.fails(lambda: 1.0 % 0, "floating-point modulo by zero") +assert.fails(lambda: 1.0 % 0.0, "floating-point modulo by zero") +assert.fails(lambda: 1 % 0.0, "floating-point modulo by zero") + +# floats cannot be used as indices, even if integral +assert.fails(lambda: "abc"[1.0], "want int") +assert.fails(lambda: ["A", "B", "C"].insert(1.0, "D"), "want int") +assert.fails(lambda: range(3)[1.0], "got float, want int") + +# -- comparisons -- +# NaN +assert.true(nan == nan) # \ +assert.true(nan >= nan) # unlike Python +assert.true(nan <= nan) # / +assert.true(not (nan > nan)) +assert.true(not (nan < nan)) +assert.true(not (nan != nan)) # unlike Python +# Sort is stable: 0.0 and -0.0 are equal, but they are not permuted. +# Similarly 1 and 1.0. +assert.eq( + str(sorted([inf, neginf, nan, 1e300, -1e300, 1.0, -1.0, 1, -1, 1e-300, -1e-300, 0, 0.0, negzero, 1e-300, -1e-300])), + "[-inf, -1e+300, -1.0, -1, -1e-300, -1e-300, 0, 0.0, -0.0, 1e-300, 1e-300, 1.0, 1, 1e+300, +inf, nan]") + +# Sort is stable, and its result contains no adjacent x, y such that y > x. +# Note: Python's reverse sort is unstable; see https://bugs.python.org/issue36095. +assert.eq(str(sorted([7, 3, nan, 1, 9])), "[1, 3, 7, 9, nan]") +assert.eq(str(sorted([7, 3, nan, 1, 9], reverse=True)), "[nan, 9, 7, 3, 1]") + +# All NaN values compare equal. (Identical objects compare equal.) +nandict = {nan: 1} +nandict[nan] = 2 +assert.eq(len(nandict), 1) # (same as Python) +assert.eq(nandict[nan], 2) # (same as Python) +assert.fails(lambda: {nan: 1, nan: 2}, "duplicate key: nan") + +nandict[float('nan')] = 3 # a distinct NaN object +assert.eq(str(nandict), "{nan: 3}") # (Python: {nan: 2, nan: 3}) + +assert.eq(str({inf: 1, neginf: 2}), "{+inf: 1, -inf: 2}") + +# zero +assert.eq(0.0, negzero) + +# inf +assert.eq(+inf / +inf, nan) +assert.eq(+inf / -inf, nan) +assert.eq(-inf / +inf, nan) +assert.eq(0.0 / +inf, 0.0) +assert.eq(0.0 / -inf, 0.0) +assert.true(inf > -inf) +assert.eq(inf, -neginf) +# TODO(adonovan): assert inf > any finite number, etc. + +# negative zero +negz = -0 +assert.eq(negz, 0) + +# min/max ordering with NaN (the greatest float value) +assert.eq(max([1, nan, 3]), nan) +assert.eq(max([nan, 2, 3]), nan) +assert.eq(min([1, nan, 3]), 1) +assert.eq(min([nan, 2, 3]), 2) + +# float/float comparisons +fltmax = 1.7976931348623157e+308 # approx +fltmin = 4.9406564584124654e-324 # approx +assert.lt(-inf, -fltmax) +assert.lt(-fltmax, -1.0) +assert.lt(-1.0, -fltmin) +assert.lt(-fltmin, 0.0) +assert.lt(0, fltmin) +assert.lt(fltmin, 1.0) +assert.lt(1.0, fltmax) +assert.lt(fltmax, inf) + +# int/float comparisons +assert.eq(0, 0.0) +assert.eq(1, 1.0) +assert.eq(-1, -1.0) +assert.ne(-1, -1.0 + 1e-7) +assert.lt(-2, -2 + 1e-15) + +# int conversion (rounds towards zero) +assert.eq(int(100.1), 100) +assert.eq(int(100.0), 100) +assert.eq(int(99.9), 99) +assert.eq(int(-99.9), -99) +assert.eq(int(-100.0), -100) +assert.eq(int(-100.1), -100) +assert.eq(int(1e100), int("10000000000000000159028911097599180468360808563945281389781327557747838772170381060813469985856815104")) +assert.fails(lambda: int(inf), "cannot convert.*infinity") +assert.fails(lambda: int(nan), "cannot convert.*NaN") + +# -- float() function -- +assert.eq(float(), 0.0) +# float(bool) +assert.eq(float(False), 0.0) +assert.eq(float(True), 1.0) +# float(int) +assert.eq(float(0), 0.0) +assert.eq(float(1), 1.0) +assert.eq(float(123), 123.0) +assert.eq(float(123 * 1000000 * 1000000 * 1000000 * 1000000 * 1000000), 1.23e+32) +# float(float) +assert.eq(float(1.1), 1.1) +assert.fails(lambda: float(None), "want number or string") +assert.ne(False, 0.0) # differs from Python +assert.ne(True, 1.0) +# float(string) +assert.eq(float("1.1"), 1.1) +assert.fails(lambda: float("1.1abc"), "invalid float literal") +assert.fails(lambda: float("1e100.0"), "invalid float literal") +assert.fails(lambda: float("1e1000"), "floating-point number too large") +assert.eq(float("-1.1"), -1.1) +assert.eq(float("+1.1"), +1.1) +assert.eq(float("+Inf"), inf) +assert.eq(float("-Inf"), neginf) +assert.eq(float("NaN"), nan) +assert.eq(float("NaN"), nan) +assert.eq(float("+NAN"), nan) +assert.eq(float("-nan"), nan) +assert.eq(str(float("Inf")), "+inf") +assert.eq(str(float("+INF")), "+inf") +assert.eq(str(float("-inf")), "-inf") +assert.eq(str(float("+InFiniTy")), "+inf") +assert.eq(str(float("-iNFiniTy")), "-inf") +assert.fails(lambda: float("one point two"), "invalid float literal: one point two") +assert.fails(lambda: float("1.2.3"), "invalid float literal: 1.2.3") +assert.fails(lambda: float(123 << 500 << 500 << 50), "int too large to convert to float") +assert.fails(lambda: float(-123 << 500 << 500 << 50), "int too large to convert to float") +assert.fails(lambda: float(str(-123 << 500 << 500 << 50)), "floating-point number too large") + +# -- implicit float(int) conversions -- +assert.fails(lambda: (1<<500<<500<<500) + 0.0, "int too large to convert to float") +assert.fails(lambda: 0.0 + (1<<500<<500<<500), "int too large to convert to float") +assert.fails(lambda: (1<<500<<500<<500) - 0.0, "int too large to convert to float") +assert.fails(lambda: 0.0 - (1<<500<<500<<500), "int too large to convert to float") +assert.fails(lambda: (1<<500<<500<<500) * 1.0, "int too large to convert to float") +assert.fails(lambda: 1.0 * (1<<500<<500<<500), "int too large to convert to float") +assert.fails(lambda: (1<<500<<500<<500) / 1.0, "int too large to convert to float") +assert.fails(lambda: 1.0 / (1<<500<<500<<500), "int too large to convert to float") +assert.fails(lambda: (1<<500<<500<<500) // 1.0, "int too large to convert to float") +assert.fails(lambda: 1.0 // (1<<500<<500<<500), "int too large to convert to float") +assert.fails(lambda: (1<<500<<500<<500) % 1.0, "int too large to convert to float") +assert.fails(lambda: 1.0 % (1<<500<<500<<500), "int too large to convert to float") + + +# -- int function -- +assert.eq(int(0.0), 0) +assert.eq(int(1.0), 1) +assert.eq(int(1.1), 1) +assert.eq(int(0.9), 0) +assert.eq(int(-1.1), -1.0) +assert.eq(int(-1.0), -1.0) +assert.eq(int(-0.9), 0.0) +assert.eq(int(1.23e+32), 123000000000000004979083645550592) +assert.eq(int(-1.23e-32), 0) +assert.eq(int(1.23e-32), 0) +assert.fails(lambda: int(float("+Inf")), "cannot convert float infinity to integer") +assert.fails(lambda: int(float("-Inf")), "cannot convert float infinity to integer") +assert.fails(lambda: int(float("NaN")), "cannot convert float NaN to integer") + + +# hash +# Check that equal float and int values have the same internal hash. +def checkhash(): + for a in [1.23e100, 1.23e10, 1.23e1, 1.23, + 1, 4294967295, 8589934591, 9223372036854775807]: + for b in [a, -a, 1/a, -1/a]: + f = float(b) + i = int(b) + if f == i: + fh = {f: None} + ih = {i: None} + if fh != ih: + assert.true(False, "{%v: None} != {%v: None}: hashes vary" % fh, ih) +checkhash() + +# string formatting + +# %d +assert.eq("%d" % 0, "0") +assert.eq("%d" % 0.0, "0") +assert.eq("%d" % 123, "123") +assert.eq("%d" % 123.0, "123") +assert.eq("%d" % 1.23e45, "1229999999999999973814869011019624571608236032") +# (see below for '%d' % NaN/Inf) +assert.eq("%d" % negzero, "0") +assert.fails(lambda: "%d" % float("NaN"), "cannot convert float NaN to integer") +assert.fails(lambda: "%d" % float("+Inf"), "cannot convert float infinity to integer") +assert.fails(lambda: "%d" % float("-Inf"), "cannot convert float infinity to integer") + +# %e +assert.eq("%e" % 0, "0.000000e+00") +assert.eq("%e" % 0.0, "0.000000e+00") +assert.eq("%e" % 123, "1.230000e+02") +assert.eq("%e" % 123.0, "1.230000e+02") +assert.eq("%e" % 1.23e45, "1.230000e+45") +assert.eq("%e" % -1.23e-45, "-1.230000e-45") +assert.eq("%e" % nan, "nan") +assert.eq("%e" % inf, "+inf") +assert.eq("%e" % neginf, "-inf") +assert.eq("%e" % negzero, "-0.000000e+00") +assert.fails(lambda: "%e" % "123", "requires float, not str") +# %f +assert.eq("%f" % 0, "0.000000") +assert.eq("%f" % 0.0, "0.000000") +assert.eq("%f" % 123, "123.000000") +assert.eq("%f" % 123.0, "123.000000") +# Note: Starlark/Java emits 1230000000000000000000000000000000000000000000.000000. Why? +assert.eq("%f" % 1.23e45, "1229999999999999973814869011019624571608236032.000000") +assert.eq("%f" % -1.23e-45, "-0.000000") +assert.eq("%f" % nan, "nan") +assert.eq("%f" % inf, "+inf") +assert.eq("%f" % neginf, "-inf") +assert.eq("%f" % negzero, "-0.000000") +assert.fails(lambda: "%f" % "123", "requires float, not str") +# %g +assert.eq("%g" % 0, "0.0") +assert.eq("%g" % 0.0, "0.0") +assert.eq("%g" % 123, "123.0") +assert.eq("%g" % 123.0, "123.0") +assert.eq("%g" % 1.110, "1.11") +assert.eq("%g" % 1e5, "100000.0") +assert.eq("%g" % 1e6, "1e+06") # Note: threshold of scientific notation is 1e17 in Starlark/Java +assert.eq("%g" % 1.23e45, "1.23e+45") +assert.eq("%g" % -1.23e-45, "-1.23e-45") +assert.eq("%g" % nan, "nan") +assert.eq("%g" % inf, "+inf") +assert.eq("%g" % neginf, "-inf") +assert.eq("%g" % negzero, "-0.0") +# str uses %g +assert.eq(str(0.0), "0.0") +assert.eq(str(123.0), "123.0") +assert.eq(str(1.23e45), "1.23e+45") +assert.eq(str(-1.23e-45), "-1.23e-45") +assert.eq(str(nan), "nan") +assert.eq(str(inf), "+inf") +assert.eq(str(neginf), "-inf") +assert.eq(str(negzero), "-0.0") +assert.fails(lambda: "%g" % "123", "requires float, not str") + +i0 = 1 +f0 = 1.0 +assert.eq(type(i0), "int") +assert.eq(type(f0), "float") + +ops = { + '+': lambda x, y: x + y, + '-': lambda x, y: x - y, + '*': lambda x, y: x * y, + '/': lambda x, y: x / y, + '//': lambda x, y: x // y, + '%': lambda x, y: x % y, +} + +# Check that if either argument is a float, so too is the result. +def checktypes(): + want = set(""" +int + int = int +int + float = float +float + int = float +float + float = float +int - int = int +int - float = float +float - int = float +float - float = float +int * int = int +int * float = float +float * int = float +float * float = float +int / int = float +int / float = float +float / int = float +float / float = float +int // int = int +int // float = float +float // int = float +float // float = float +int % int = int +int % float = float +float % int = float +float % float = float +"""[1:].splitlines()) + for opname in ("+", "-", "*", "/", "%"): + for x in [i0, f0]: + for y in [i0, f0]: + op = ops[opname] + got = "%s %s %s = %s" % (type(x), opname, type(y), type(op(x, y))) + assert.contains(want, got) +checktypes() diff --git a/starlark/testdata/function.star b/starlark/testdata/function.star new file mode 100644 index 0000000..737df26 --- /dev/null +++ b/starlark/testdata/function.star @@ -0,0 +1,323 @@ +# Tests of Starlark 'function' +# option:set + +# TODO(adonovan): +# - add some introspection functions for looking at function values +# and test that functions have correct position, free vars, names of locals, etc. +# - move the hard-coded tests of parameter passing from eval_test.go to here. + +load("assert.star", "assert", "freeze") + +# Test lexical scope and closures: +def outer(x): + def inner(y): + return x + x + y # multiple occurrences of x should create only 1 freevar + return inner + +z = outer(3) +assert.eq(z(5), 11) +assert.eq(z(7), 13) +z2 = outer(4) +assert.eq(z2(5), 13) +assert.eq(z2(7), 15) +assert.eq(z(5), 11) +assert.eq(z(7), 13) + +# Function name +assert.eq(str(outer), '<function outer>') +assert.eq(str(z), '<function inner>') +assert.eq(str(str), '<built-in function str>') +assert.eq(str("".startswith), '<built-in method startswith of string value>') + +# Stateful closure +def squares(): + x = [0] + def f(): + x[0] += 1 + return x[0] * x[0] + return f + +sq = squares() +assert.eq(sq(), 1) +assert.eq(sq(), 4) +assert.eq(sq(), 9) +assert.eq(sq(), 16) + +# Freezing a closure +sq2 = freeze(sq) +assert.fails(sq2, "frozen list") + +# recursion detection, simple +def fib(x): + if x < 2: + return x + return fib(x-2) + fib(x-1) +assert.fails(lambda: fib(10), "function fib called recursively") + +# recursion detection, advanced +# +# A simplistic recursion check that looks for repeated calls to the +# same function value will not detect recursion using the Y +# combinator, which creates a new closure at each step of the +# recursion. To truly prohibit recursion, the dynamic check must look +# for repeated calls of the same syntactic function body. +Y = lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args))) +fibgen = lambda fib: lambda x: (x if x<2 else fib(x-1)+fib(x-2)) +fib2 = Y(fibgen) +assert.fails(lambda: [fib2(x) for x in range(10)], "function lambda called recursively") + +# However, this stricter check outlaws many useful programs +# that are still bounded, and creates a hazard because +# helper functions such as map below cannot be used to +# call functions that themselves use map: +def map(f, seq): return [f(x) for x in seq] +def double(x): return x+x +assert.eq(map(double, [1, 2, 3]), [2, 4, 6]) +assert.eq(map(double, ["a", "b", "c"]), ["aa", "bb", "cc"]) +def mapdouble(x): return map(double, x) +assert.fails(lambda: map(mapdouble, ([1, 2, 3], ["a", "b", "c"])), + 'function map called recursively') +# With the -recursion option it would yield [[2, 4, 6], ["aa", "bb", "cc"]]. + +# call of function not through its name +# (regression test for parsing suffixes of primary expressions) +hf = hasfields() +hf.x = [len] +assert.eq(hf.x[0]("abc"), 3) +def f(): + return lambda: 1 +assert.eq(f()(), 1) +assert.eq(["abc"][0][0].upper(), "A") + +# functions may be recursively defined, +# so long as they don't dynamically recur. +calls = [] +def yin(x): + calls.append("yin") + if x: + yang(False) + +def yang(x): + calls.append("yang") + if x: + yin(False) + +yin(True) +assert.eq(calls, ["yin", "yang"]) + +calls.clear() +yang(True) +assert.eq(calls, ["yang", "yin"]) + + +# builtin_function_or_method use identity equivalence. +closures = set(["".count for _ in range(10)]) +assert.eq(len(closures), 10) + +--- +# Default values of function parameters are mutable. +load("assert.star", "assert", "freeze") + +def f(x=[0]): + return x + +assert.eq(f(), [0]) + +f().append(1) +assert.eq(f(), [0, 1]) + +# Freezing a function value freezes its parameter defaults. +freeze(f) +assert.fails(lambda: f().append(2), "cannot append to frozen list") + +--- +# This is a well known corner case of parsing in Python. +load("assert.star", "assert") + +f = lambda x: 1 if x else 0 +assert.eq(f(True), 1) +assert.eq(f(False), 0) + +x = True +f2 = (lambda x: 1) if x else 0 +assert.eq(f2(123), 1) + +tf = lambda: True, lambda: False +assert.true(tf[0]()) +assert.true(not tf[1]()) + +--- +# Missing parameters are correctly reported +# in functions of more than 64 parameters. +# (This tests a corner case of the implementation: +# we avoid a map allocation for <64 parameters) + +load("assert.star", "assert") + +def f(a, b, c, d, e, f, g, h, + i, j, k, l, m, n, o, p, + q, r, s, t, u, v, w, x, + y, z, A, B, C, D, E, F, + G, H, I, J, K, L, M, N, + O, P, Q, R, S, T, U, V, + W, X, Y, Z, aa, bb, cc, dd, + ee, ff, gg, hh, ii, jj, kk, ll, + mm): + pass + +assert.fails(lambda: f( + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63, 64), "missing 1 argument \\(mm\\)") + +assert.fails(lambda: f( + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63, 64, 65, + mm = 100), 'multiple values for parameter "mm"') + +--- +# Regression test for github.com/google/starlark-go/issues/21, +# which concerns dynamic checks. +# Related: https://github.com/bazelbuild/starlark/issues/21, +# which concerns static checks. + +load("assert.star", "assert") + +def f(*args, **kwargs): + return args, kwargs + +assert.eq(f(x=1, y=2), ((), {"x": 1, "y": 2})) +assert.fails(lambda: f(x=1, **dict(x=2)), 'multiple values for parameter "x"') + +def g(x, y): + return x, y + +assert.eq(g(1, y=2), (1, 2)) +assert.fails(lambda: g(1, y=2, **{'y': 3}), 'multiple values for parameter "y"') + +--- +# Regression test for a bug in CALL_VAR_KW. + +load("assert.star", "assert") + +def f(a, b, x, y): + return a+b+x+y + +assert.eq(f(*("a", "b"), **dict(y="y", x="x")) + ".", 'abxy.') +--- +# Order of evaluation of function arguments. +# Regression test for github.com/google/skylark/issues/135. +load("assert.star", "assert") + +r = [] + +def id(x): + r.append(x) + return x + +def f(*args, **kwargs): + return (args, kwargs) + +y = f(id(1), id(2), x=id(3), *[id(4)], **dict(z=id(5))) +assert.eq(y, ((1, 2, 4), dict(x=3, z=5))) + +# This matches Python2 and Starlark-in-Java, but not Python3 [1 2 4 3 6]. +# *args and *kwargs are evaluated last. +# (Python[23] also allows keyword arguments after *args.) +# See github.com/bazelbuild/starlark#13 for spec change. +assert.eq(r, [1, 2, 3, 4, 5]) + +--- +# option:recursion +# See github.com/bazelbuild/starlark#170 +load("assert.star", "assert") + +def a(): + list = [] + def b(n): + list.append(n) + if n > 0: + b(n - 1) # recursive reference to b + + b(3) + return list + +assert.eq(a(), [3, 2, 1, 0]) + +def c(): + list = [] + x = 1 + def d(): + list.append(x) # this use of x observes both assignments + d() + x = 2 + d() + return list + +assert.eq(c(), [1, 2]) + +def e(): + def f(): + return x # forward reference ok: x is a closure cell + x = 1 + return f() + +assert.eq(e(), 1) + +--- +load("assert.star", "assert") + +def e(): + x = 1 + def f(): + print(x) # this reference to x fails + x = 3 # because this assignment makes x local to f + f() + +assert.fails(e, "local variable x referenced before assignment") + +def f(): + def inner(): + return x + if False: + x = 0 + return x # fails (x is an uninitialized cell of this function) + +assert.fails(f, "local variable x referenced before assignment") + +def g(): + def inner(): + return x # fails (x is an uninitialized cell of the enclosing function) + if False: + x = 0 + return inner() + +assert.fails(g, "local variable x referenced before assignment") + +--- +# A trailing comma is allowed in any function definition or call. +# This reduces the need to edit neighboring lines when editing defs +# or calls splayed across multiple lines. + +def a(x,): pass +def b(x, y=None, ): pass +def c(x, y=None, *args, ): pass +def d(x, y=None, *args, z=None, ): pass +def e(x, y=None, *args, z=None, **kwargs, ): pass + +a(1,) +b(1, y=2, ) +#c(1, *[], ) +#d(1, *[], z=None, ) +#e(1, *[], z=None, *{}, ) diff --git a/starlark/testdata/int.star b/starlark/testdata/int.star new file mode 100644 index 0000000..46c0ad0 --- /dev/null +++ b/starlark/testdata/int.star @@ -0,0 +1,260 @@ +# Tests of Starlark 'int' + +load("assert.star", "assert") + +# basic arithmetic +assert.eq(0 - 1, -1) +assert.eq(0 + 1, +1) +assert.eq(1 + 1, 2) +assert.eq(5 + 7, 12) +assert.eq(5 * 7, 35) +assert.eq(5 - 7, -2) + +# int boundaries +maxint64 = (1 << 63) - 1 +minint64 = -1 << 63 +maxint32 = (1 << 31) - 1 +minint32 = -1 << 31 +assert.eq(maxint64, 9223372036854775807) +assert.eq(minint64, -9223372036854775808) +assert.eq(maxint32, 2147483647) +assert.eq(minint32, -2147483648) + +# truth +def truth(): + assert.true(not 0) + for m in [1, maxint32]: # Test small/big ranges + assert.true(123 * m) + assert.true(-1 * m) + +truth() + +# floored division +# (For real division, see float.star.) +def division(): + for m in [1, maxint32]: # Test small/big ranges + assert.eq((100 * m) // (7 * m), 14) + assert.eq((100 * m) // (-7 * m), -15) + assert.eq((-100 * m) // (7 * m), -15) # NB: different from Go/Java + assert.eq((-100 * m) // (-7 * m), 14) # NB: different from Go/Java + assert.eq((98 * m) // (7 * m), 14) + assert.eq((98 * m) // (-7 * m), -14) + assert.eq((-98 * m) // (7 * m), -14) + assert.eq((-98 * m) // (-7 * m), 14) + +division() + +# remainder +def remainder(): + for m in [1, maxint32]: # Test small/big ranges + assert.eq((100 * m) % (7 * m), 2 * m) + assert.eq((100 * m) % (-7 * m), -5 * m) # NB: different from Go/Java + assert.eq((-100 * m) % (7 * m), 5 * m) # NB: different from Go/Java + assert.eq((-100 * m) % (-7 * m), -2 * m) + assert.eq((98 * m) % (7 * m), 0) + assert.eq((98 * m) % (-7 * m), 0) + assert.eq((-98 * m) % (7 * m), 0) + assert.eq((-98 * m) % (-7 * m), 0) + +remainder() + +# compound assignment +def compound(): + x = 1 + x += 1 + assert.eq(x, 2) + x -= 3 + assert.eq(x, -1) + x *= 39 + assert.eq(x, -39) + x //= 4 + assert.eq(x, -10) + x /= -2 + assert.eq(x, 5) + x %= 3 + assert.eq(x, 2) + + # use resolve.AllowBitwise to enable the ops: + x = 2 + x &= 1 + assert.eq(x, 0) + x |= 2 + assert.eq(x, 2) + x ^= 3 + assert.eq(x, 1) + x <<= 2 + assert.eq(x, 4) + x >>= 2 + assert.eq(x, 1) + +compound() + +# int conversion +# See float.star for float-to-int conversions. +# We follow Python 3 here, but I can't see the method in its madness. +# int from bool/int/float +assert.fails(int, "missing argument") # int() +assert.eq(int(False), 0) +assert.eq(int(True), 1) +assert.eq(int(3), 3) +assert.eq(int(3.1), 3) +assert.fails(lambda: int(3, base = 10), "non-string with explicit base") +assert.fails(lambda: int(True, 10), "non-string with explicit base") + +# int from string, base implicitly 10 +assert.eq(int("100000000000000000000"), 10000000000 * 10000000000) +assert.eq(int("-100000000000000000000"), -10000000000 * 10000000000) +assert.eq(int("123"), 123) +assert.eq(int("-123"), -123) +assert.eq(int("0123"), 123) # not octal +assert.eq(int("-0123"), -123) +assert.fails(lambda: int("0x12"), "invalid literal with base 10") +assert.fails(lambda: int("-0x12"), "invalid literal with base 10") +assert.fails(lambda: int("0o123"), "invalid literal.*base 10") +assert.fails(lambda: int("-0o123"), "invalid literal.*base 10") + +# int from string, explicit base +assert.eq(int("0"), 0) +assert.eq(int("00"), 0) +assert.eq(int("0", base = 10), 0) +assert.eq(int("00", base = 10), 0) +assert.eq(int("0", base = 8), 0) +assert.eq(int("00", base = 8), 0) +assert.eq(int("-0"), 0) +assert.eq(int("-00"), 0) +assert.eq(int("-0", base = 10), 0) +assert.eq(int("-00", base = 10), 0) +assert.eq(int("-0", base = 8), 0) +assert.eq(int("-00", base = 8), 0) +assert.eq(int("+0"), 0) +assert.eq(int("+00"), 0) +assert.eq(int("+0", base = 10), 0) +assert.eq(int("+00", base = 10), 0) +assert.eq(int("+0", base = 8), 0) +assert.eq(int("+00", base = 8), 0) +assert.eq(int("11", base = 9), 10) +assert.eq(int("-11", base = 9), -10) +assert.eq(int("10011", base = 2), 19) +assert.eq(int("-10011", base = 2), -19) +assert.eq(int("123", 8), 83) +assert.eq(int("-123", 8), -83) +assert.eq(int("0123", 8), 83) # redundant zeros permitted +assert.eq(int("-0123", 8), -83) +assert.eq(int("00123", 8), 83) +assert.eq(int("-00123", 8), -83) +assert.eq(int("0o123", 8), 83) +assert.eq(int("-0o123", 8), -83) +assert.eq(int("123", 7), 66) # 1*7*7 + 2*7 + 3 +assert.eq(int("-123", 7), -66) +assert.eq(int("12", 16), 18) +assert.eq(int("-12", 16), -18) +assert.eq(int("0x12", 16), 18) +assert.eq(int("-0x12", 16), -18) +assert.eq(0x1000000000000001 * 0x1000000000000001, 0x1000000000000002000000000000001) +assert.eq(int("1010", 2), 10) +assert.eq(int("111111101", 2), 509) +assert.eq(int("0b0101", 0), 5) +assert.eq(int("0b0101", 2), 5) # prefix is redundant with explicit base +assert.eq(int("0b00000", 0), 0) +assert.eq(1111111111111111 * 1111111111111111, 1234567901234567654320987654321) +assert.fails(lambda: int("0x123", 8), "invalid literal.*base 8") +assert.fails(lambda: int("-0x123", 8), "invalid literal.*base 8") +assert.fails(lambda: int("0o123", 16), "invalid literal.*base 16") +assert.fails(lambda: int("-0o123", 16), "invalid literal.*base 16") +assert.fails(lambda: int("0x110", 2), "invalid literal.*base 2") + +# Base prefix is honored only if base=0, or if the prefix matches the explicit base. +# See https://github.com/google/starlark-go/issues/337 +assert.fails(lambda: int("0b0"), "invalid literal.*base 10") +assert.eq(int("0b0", 0), 0) +assert.eq(int("0b0", 2), 0) +assert.eq(int("0b0", 16), 0xb0) +assert.eq(int("0x0b0", 16), 0xb0) +assert.eq(int("0x0b0", 0), 0xb0) +assert.eq(int("0x0b0101", 16), 0x0b0101) + +# int from string, auto detect base +assert.eq(int("123", 0), 123) +assert.eq(int("+123", 0), +123) +assert.eq(int("-123", 0), -123) +assert.eq(int("0x12", 0), 18) +assert.eq(int("+0x12", 0), +18) +assert.eq(int("-0x12", 0), -18) +assert.eq(int("0o123", 0), 83) +assert.eq(int("+0o123", 0), +83) +assert.eq(int("-0o123", 0), -83) +assert.fails(lambda: int("0123", 0), "invalid literal.*base 0") # valid in Python 2.7 +assert.fails(lambda: int("-0123", 0), "invalid literal.*base 0") + +# github.com/google/starlark-go/issues/108 +assert.fails(lambda: int("0Oxa", 8), "invalid literal with base 8: 0Oxa") + +# follow-on bugs to issue 108 +assert.fails(lambda: int("--4"), "invalid literal with base 10: --4") +assert.fails(lambda: int("++4"), "invalid literal with base 10: \\+\\+4") +assert.fails(lambda: int("+-4"), "invalid literal with base 10: \\+-4") +assert.fails(lambda: int("0x-4", 16), "invalid literal with base 16: 0x-4") + +# bitwise union (int|int), intersection (int&int), XOR (int^int), unary not (~int), +# left shift (int<<int), and right shift (int>>int). +# use resolve.AllowBitwise to enable the ops. +# TODO(adonovan): this is not yet in the Starlark spec, +# but there is consensus that it should be. +assert.eq(1 | 2, 3) +assert.eq(3 | 6, 7) +assert.eq((1 | 2) & (2 | 4), 2) +assert.eq(1 ^ 2, 3) +assert.eq(2 ^ 2, 0) +assert.eq(1 | 0 ^ 1, 1) # check | and ^ operators precedence +assert.eq(~1, -2) +assert.eq(~(-2), 1) +assert.eq(~0, -1) +assert.eq(1 << 2, 4) +assert.eq(2 >> 1, 1) +assert.fails(lambda: 2 << -1, "negative shift count") +assert.fails(lambda: 1 << 512, "shift count too large") + +# comparisons +# TODO(adonovan): test: < > == != etc +def comparisons(): + for m in [1, maxint32 / 2, maxint32]: # Test small/big ranges + assert.lt(-2 * m, -1 * m) + assert.lt(-1 * m, 0 * m) + assert.lt(0 * m, 1 * m) + assert.lt(1 * m, 2 * m) + assert.true(2 * m >= 2 * m) + assert.true(2 * m > 1 * m) + assert.true(1 * m >= 1 * m) + assert.true(1 * m > 0 * m) + assert.true(0 * m >= 0 * m) + assert.true(0 * m > -1 * m) + assert.true(-1 * m >= -1 * m) + assert.true(-1 * m > -2 * m) + +comparisons() + +# precision +assert.eq(str(maxint64), "9223372036854775807") +assert.eq(str(maxint64 + 1), "9223372036854775808") +assert.eq(str(minint64), "-9223372036854775808") +assert.eq(str(minint64 - 1), "-9223372036854775809") +assert.eq(str(minint64 * minint64), "85070591730234615865843651857942052864") +assert.eq(str(maxint32 + 1), "2147483648") +assert.eq(str(minint32 - 1), "-2147483649") +assert.eq(str(minint32 * minint32), "4611686018427387904") +assert.eq(str(minint32 | maxint32), "-1") +assert.eq(str(minint32 & minint32), "-2147483648") +assert.eq(str(minint32 ^ maxint32), "-1") +assert.eq(str(minint32 // -1), "2147483648") + +# string formatting +assert.eq("%o %x %d" % (0o755, 0xDEADBEEF, 42), "755 deadbeef 42") +nums = [-95, -1, 0, +1, +95] +assert.eq(" ".join(["%o" % x for x in nums]), "-137 -1 0 1 137") +assert.eq(" ".join(["%d" % x for x in nums]), "-95 -1 0 1 95") +assert.eq(" ".join(["%i" % x for x in nums]), "-95 -1 0 1 95") +assert.eq(" ".join(["%x" % x for x in nums]), "-5f -1 0 1 5f") +assert.eq(" ".join(["%X" % x for x in nums]), "-5F -1 0 1 5F") +assert.eq("%o %x %d" % (123, 123, 123), "173 7b 123") +assert.eq("%o %x %d" % (123.1, 123.1, 123.1), "173 7b 123") # non-int operands are acceptable +assert.fails(lambda: "%d" % True, "cannot convert bool to int") diff --git a/starlark/testdata/json.star b/starlark/testdata/json.star new file mode 100644 index 0000000..7c7b316 --- /dev/null +++ b/starlark/testdata/json.star @@ -0,0 +1,147 @@ +# Tests of json module. + +load("assert.star", "assert") +load("json.star", "json") + +assert.eq(dir(json), ["decode", "encode", "indent"]) + +# Some of these cases were inspired by github.com/nst/JSONTestSuite. + +## json.encode + +assert.eq(json.encode(None), "null") +assert.eq(json.encode(True), "true") +assert.eq(json.encode(False), "false") +assert.eq(json.encode(-123), "-123") +assert.eq(json.encode(12345*12345*12345*12345*12345*12345), "3539537889086624823140625") +assert.eq(json.encode(float(12345*12345*12345*12345*12345*12345)), "3.539537889086625e+24") +assert.eq(json.encode(12.345e67), "1.2345e+68") +assert.eq(json.encode("hello"), '"hello"') +assert.eq(json.encode([1, 2, 3]), "[1,2,3]") +assert.eq(json.encode((1, 2, 3)), "[1,2,3]") +assert.eq(json.encode(range(3)), "[0,1,2]") # a built-in iterable +assert.eq(json.encode(dict(x = 1, y = "two")), '{"x":1,"y":"two"}') +assert.eq(json.encode(dict(y = "two", x = 1)), '{"x":1,"y":"two"}') # key, not insertion, order +assert.eq(json.encode(struct(x = 1, y = "two")), '{"x":1,"y":"two"}') # a user-defined HasAttrs +assert.eq(json.encode("😹"[:1]), '"\\ufffd"') # invalid UTF-8 -> replacement char + +def encode_error(expr, error): + assert.fails(lambda: json.encode(expr), error) + +encode_error(float("NaN"), "json.encode: cannot encode non-finite float nan") +encode_error({1: "two"}, "dict has int key, want string") +encode_error(len, "cannot encode builtin_function_or_method as JSON") +encode_error(struct(x=[1, {"x": len}]), # nested failure + 'in field .x: at list index 1: in dict key "x": cannot encode...') +encode_error(struct(x=[1, {"x": len}]), # nested failure + 'in field .x: at list index 1: in dict key "x": cannot encode...') +encode_error({1: 2}, 'dict has int key, want string') + +## json.decode + +assert.eq(json.decode("null"), None) +assert.eq(json.decode("true"), True) +assert.eq(json.decode("false"), False) +assert.eq(json.decode("-123"), -123) +assert.eq(json.decode("-0"), -0) +assert.eq(json.decode("3539537889086624823140625"), 3539537889086624823140625) +assert.eq(json.decode("3539537889086624823140625.0"), float(3539537889086624823140625)) +assert.eq(json.decode("3.539537889086625e+24"), 3.539537889086625e+24) +assert.eq(json.decode("0e+1"), 0) +assert.eq(json.decode("-0.0"), -0.0) +assert.eq(json.decode( + "-0.000000000000000000000000000000000000000000000000000000000000000000000000000001"), + -0.000000000000000000000000000000000000000000000000000000000000000000000000000001) +assert.eq(json.decode('[]'), []) +assert.eq(json.decode('[1]'), [1]) +assert.eq(json.decode('[1,2,3]'), [1, 2, 3]) +assert.eq(json.decode('{"one": 1, "two": 2}'), dict(one=1, two=2)) +assert.eq(json.decode('{"foo\\u0000bar": 42}'), {"foo\x00bar": 42}) +assert.eq(json.decode('"\\ud83d\\ude39\\ud83d\\udc8d"'), "😹💍") +assert.eq(json.decode('"\\u0123"'), 'ģ') +assert.eq(json.decode('"\x7f"'), "\x7f") + +def decode_error(expr, error): + assert.fails(lambda: json.decode(expr), error) + +decode_error('truefalse', + "json.decode: at offset 4, unexpected character 'f' after value") + +decode_error('"abc', "unclosed string literal") +decode_error('"ab\\gc"', "invalid character 'g' in string escape code") +decode_error("'abc'", "unexpected character '\\\\''") + +decode_error("1.2.3", "invalid number: 1.2.3") +decode_error("+1", "unexpected character '\\+'") +decode_error("-abc", "invalid number: -") +decode_error("-", "invalid number: -") +decode_error("-00", "invalid number: -00") +decode_error("00", "invalid number: 00") +decode_error("--1", "invalid number: --1") +decode_error("-+1", "invalid number: -\\+1") +decode_error("1e1e1", "invalid number: 1e1e1") +decode_error("0123", "invalid number: 0123") +decode_error("000.123", "invalid number: 000.123") +decode_error("-0123", "invalid number: -0123") +decode_error("-000.123", "invalid number: -000.123") +decode_error("0x123", "unexpected character 'x' after value") + +decode_error('[1, 2 ', "unexpected end of file") +decode_error('[1, 2, ', "unexpected end of file") +decode_error('[1, 2, ]', "unexpected character ']'") +decode_error('[1, 2, }', "unexpected character '}'") +decode_error('[1, 2}', "got '}', want ',' or ']'") + +decode_error('{"one": 1', "unexpected end of file") +decode_error('{"one" 1', "after object key, got '1', want ':'") +decode_error('{"one": 1 "two": 2', "in object, got '\"', want ',' or '}'") +decode_error('{"one": 1,', "unexpected end of file") +decode_error('{"one": 1, }', "unexpected character '}'") +decode_error('{"one": 1]', "in object, got ']', want ',' or '}'") + +def codec(x): + return json.decode(json.encode(x)) + +# string round-tripping +strings = [ + "😿", # U+1F63F CRYING_CAT_FACE + "🐱👤", # CAT FACE + ZERO WIDTH JOINER + BUST IN SILHOUETTE +] +assert.eq(codec(strings), strings) + +# codepoints is a string with every 16-bit code point. +codepoints = ''.join(['%c' % c for c in range(65536)]) +assert.eq(codec(codepoints), codepoints) + +# number round-tripping +numbers = [ + 0, 1, -1, +1, 1.23e45, -1.23e-45, + 3539537889086624823140625, + float(3539537889086624823140625), +] +assert.eq(codec(numbers), numbers) + +## json.indent + +s = json.encode(dict(x = 1, y = ["one", "two"])) + +assert.eq(json.indent(s), '''{ + "x": 1, + "y": [ + "one", + "two" + ] +}''') + +assert.eq(json.decode(json.indent(s)), {"x": 1, "y": ["one", "two"]}) + +assert.eq(json.indent(s, prefix='¶', indent='–––'), '''{ +¶–––"x": 1, +¶–––"y": [ +¶––––––"one", +¶––––––"two" +¶–––] +¶}''') + +assert.fails(lambda: json.indent("!@#$%^& this is not json"), 'invalid character') +--- diff --git a/starlark/testdata/list.star b/starlark/testdata/list.star new file mode 100644 index 0000000..526a962 --- /dev/null +++ b/starlark/testdata/list.star @@ -0,0 +1,276 @@ +# Tests of Starlark 'list' + +load("assert.star", "assert", "freeze") + +# literals +assert.eq([], []) +assert.eq([1], [1]) +assert.eq([1], [1]) +assert.eq([1, 2], [1, 2]) +assert.ne([1, 2, 3], [1, 2, 4]) + +# truth +assert.true([0]) +assert.true(not []) + +# indexing, x[i] +abc = list("abc".elems()) +assert.fails(lambda: abc[-4], "list index -4 out of range \\[-3:2]") +assert.eq(abc[-3], "a") +assert.eq(abc[-2], "b") +assert.eq(abc[-1], "c") +assert.eq(abc[0], "a") +assert.eq(abc[1], "b") +assert.eq(abc[2], "c") +assert.fails(lambda: abc[3], "list index 3 out of range \\[-3:2]") + +# x[i] = ... +x3 = [0, 1, 2] +x3[1] = 2 +x3[2] += 3 +assert.eq(x3, [0, 2, 5]) + +def f2(): + x3[3] = 4 + +assert.fails(f2, "out of range") +freeze(x3) + +def f3(): + x3[0] = 0 + +assert.fails(f3, "cannot assign to element of frozen list") +assert.fails(x3.clear, "cannot clear frozen list") + +# list + list +assert.eq([1, 2, 3] + [3, 4, 5], [1, 2, 3, 3, 4, 5]) +assert.fails(lambda: [1, 2] + (3, 4), "unknown.*list \\+ tuple") +assert.fails(lambda: (1, 2) + [3, 4], "unknown.*tuple \\+ list") + +# list * int, int * list +assert.eq(abc * 0, []) +assert.eq(abc * -1, []) +assert.eq(abc * 1, abc) +assert.eq(abc * 3, ["a", "b", "c", "a", "b", "c", "a", "b", "c"]) +assert.eq(0 * abc, []) +assert.eq(-1 * abc, []) +assert.eq(1 * abc, abc) +assert.eq(3 * abc, ["a", "b", "c", "a", "b", "c", "a", "b", "c"]) + +# list comprehensions +assert.eq([2 * x for x in [1, 2, 3]], [2, 4, 6]) +assert.eq([2 * x for x in [1, 2, 3] if x > 1], [4, 6]) +assert.eq( + [(x, y) for x in [1, 2] for y in [3, 4]], + [(1, 3), (1, 4), (2, 3), (2, 4)], +) +assert.eq([(x, y) for x in [1, 2] if x == 2 for y in [3, 4]], [(2, 3), (2, 4)]) +assert.eq([2 * x for x in (1, 2, 3)], [2, 4, 6]) +assert.eq([x for x in "abc".elems()], ["a", "b", "c"]) +assert.eq([x for x in {"a": 1, "b": 2}], ["a", "b"]) +assert.eq([(y, x) for x, y in {1: 2, 3: 4}.items()], [(2, 1), (4, 3)]) + +# corner cases of parsing: +assert.eq([x for x in range(12) if x % 2 == 0 if x % 3 == 0], [0, 6]) +assert.eq([x for x in [1, 2] if lambda: None], [1, 2]) +assert.eq([x for x in [1, 2] if (lambda: 3 if True else 4)], [1, 2]) + +# list function +assert.eq(list(), []) +assert.eq(list("ab".elems()), ["a", "b"]) + +# A list comprehension defines a separate lexical block, +# whether at top-level... +a = [1, 2] +b = [a for a in [3, 4]] +assert.eq(a, [1, 2]) +assert.eq(b, [3, 4]) + +# ...or local to a function. +def listcompblock(): + c = [1, 2] + d = [c for c in [3, 4]] + assert.eq(c, [1, 2]) + assert.eq(d, [3, 4]) + +listcompblock() + +# list.pop +x4 = [1, 2, 3, 4, 5] +assert.fails(lambda: x4.pop(-6), "index -6 out of range \\[-5:4]") +assert.fails(lambda: x4.pop(6), "index 6 out of range \\[-5:4]") +assert.eq(x4.pop(), 5) +assert.eq(x4, [1, 2, 3, 4]) +assert.eq(x4.pop(1), 2) +assert.eq(x4, [1, 3, 4]) +assert.eq(x4.pop(0), 1) +assert.eq(x4, [3, 4]) +assert.eq(x4.pop(-2), 3) +assert.eq(x4, [4]) +assert.eq(x4.pop(-1), 4) +assert.eq(x4, []) + +# TODO(adonovan): test uses of list as sequence +# (for loop, comprehension, library functions). + +# x += y for lists is equivalent to x.extend(y). +# y may be a sequence. +# TODO: Test that side-effects of 'x' occur only once. +def list_extend(): + a = [1, 2, 3] + b = a + a = a + [4] # creates a new list + assert.eq(a, [1, 2, 3, 4]) + assert.eq(b, [1, 2, 3]) # b is unchanged + + a = [1, 2, 3] + b = a + a += [4] # updates a (and thus b) in place + assert.eq(a, [1, 2, 3, 4]) + assert.eq(b, [1, 2, 3, 4]) # alias observes the change + + a = [1, 2, 3] + b = a + a.extend([4]) # updates existing list + assert.eq(a, [1, 2, 3, 4]) + assert.eq(b, [1, 2, 3, 4]) # alias observes the change + +list_extend() + +# Unlike list.extend(iterable), list += iterable makes its LHS name local. +a_list = [] + +def f4(): + a_list += [1] # binding use => a_list is a local var + +assert.fails(f4, "local variable a_list referenced before assignment") + +# list += <not iterable> +def f5(): + x = [] + x += 1 + +assert.fails(f5, "unknown binary op: list \\+ int") + +# frozen list += iterable +def f6(): + x = [] + freeze(x) + x += [1] + +assert.fails(f6, "cannot apply \\+= to frozen list") + +# list += hasfields (hasfields is not iterable but defines list+hasfields) +def f7(): + x = [] + x += hasfields() + return x + +assert.eq(f7(), 42) # weird, but exercises a corner case in list+=x. + +# append +x5 = [1, 2, 3] +x5.append(4) +x5.append("abc") +assert.eq(x5, [1, 2, 3, 4, "abc"]) + +# extend +x5a = [1, 2, 3] +x5a.extend("abc".elems()) # string +x5a.extend((True, False)) # tuple +assert.eq(x5a, [1, 2, 3, "a", "b", "c", True, False]) + +# list.insert +def insert_at(index): + x = list(range(3)) + x.insert(index, 42) + return x + +assert.eq(insert_at(-99), [42, 0, 1, 2]) +assert.eq(insert_at(-2), [0, 42, 1, 2]) +assert.eq(insert_at(-1), [0, 1, 42, 2]) +assert.eq(insert_at(0), [42, 0, 1, 2]) +assert.eq(insert_at(1), [0, 42, 1, 2]) +assert.eq(insert_at(2), [0, 1, 42, 2]) +assert.eq(insert_at(3), [0, 1, 2, 42]) +assert.eq(insert_at(4), [0, 1, 2, 42]) + +# list.remove +def remove(v): + x = [3, 1, 4, 1] + x.remove(v) + return x + +assert.eq(remove(3), [1, 4, 1]) +assert.eq(remove(1), [3, 4, 1]) +assert.eq(remove(4), [3, 1, 1]) +assert.fails(lambda: [3, 1, 4, 1].remove(42), "remove: element not found") + +# list.index +bananas = list("bananas".elems()) +assert.eq(bananas.index("a"), 1) # bAnanas +assert.fails(lambda: bananas.index("d"), "value not in list") + +# start +assert.eq(bananas.index("a", -1000), 1) # bAnanas +assert.eq(bananas.index("a", 0), 1) # bAnanas +assert.eq(bananas.index("a", 1), 1) # bAnanas +assert.eq(bananas.index("a", 2), 3) # banAnas +assert.eq(bananas.index("a", 3), 3) # banAnas +assert.eq(bananas.index("b", 0), 0) # Bananas +assert.eq(bananas.index("n", -3), 4) # banaNas +assert.fails(lambda: bananas.index("n", -2), "value not in list") +assert.eq(bananas.index("s", -2), 6) # bananaS +assert.fails(lambda: bananas.index("b", 1), "value not in list") + +# start, end +assert.eq(bananas.index("s", -1000, 7), 6) # bananaS +assert.fails(lambda: bananas.index("s", -1000, 6), "value not in list") +assert.fails(lambda: bananas.index("d", -1000, 1000), "value not in list") + +# slicing, x[i:j:k] +assert.eq(bananas[6::-2], list("snnb".elems())) +assert.eq(bananas[5::-2], list("aaa".elems())) +assert.eq(bananas[4::-2], list("nnb".elems())) +assert.eq(bananas[99::-2], list("snnb".elems())) +assert.eq(bananas[100::-2], list("snnb".elems())) +# TODO(adonovan): many more tests + +# iterator invalidation +def iterator1(): + list = [0, 1, 2] + for x in list: + list[x] = 2 * x + return list + +assert.fails(iterator1, "assign to element.* during iteration") + +def iterator2(): + list = [0, 1, 2] + for x in list: + list.remove(x) + +assert.fails(iterator2, "remove.*during iteration") + +def iterator3(): + list = [0, 1, 2] + for x in list: + list.append(3) + +assert.fails(iterator3, "append.*during iteration") + +def iterator4(): + list = [0, 1, 2] + for x in list: + list.extend([3, 4]) + +assert.fails(iterator4, "extend.*during iteration") + +def iterator5(): + def f(x): + x.append(4) + + list = [1, 2, 3] + _ = [f(list) for x in list] + +assert.fails(iterator5, "append.*during iteration") diff --git a/starlark/testdata/misc.star b/starlark/testdata/misc.star new file mode 100644 index 0000000..e7e0c06 --- /dev/null +++ b/starlark/testdata/misc.star @@ -0,0 +1,139 @@ +# Miscellaneous tests of Starlark evaluation. +# This is a "chunked" file: each "---" effectively starts a new file. + +# TODO(adonovan): move these tests into more appropriate files. +# TODO(adonovan): test coverage: +# - stmts: pass; if cond fail; += and failures; +# for x fail; for x not iterable; for can't assign; for +# error in loop body +# - subassign fail +# - x[i]=x fail in both operands; frozen x; list index not int; boundscheck +# - x.f = ... +# - failure in list expr [...]; tuple expr; dict expr (bad key) +# - cond expr semantics; failures +# - x[i] failures in both args; dict and iterator key and range checks; +# unhandled operand types +# - +: list/list, int/int, string/string, tuple+tuple, dict/dict; +# - * and ** calls: various errors +# - call of non-function +# - slice x[ijk] +# - comprehension: unhashable dict key; +# scope of vars (local and toplevel); noniterable for clause +# - unknown unary op +# - ordering of values +# - freeze, transitivity of its effect. +# - add an application-defined type to the environment so we can test it. +# - even more: +# +# eval +# pass statement +# assign to tuple l-value -- illegal +# assign to list l-value -- illegal +# assign to field +# tuple + tuple +# call with *args, **kwargs +# slice with step +# tuple slice +# interpolate with %c, %% + +load("assert.star", "assert") + +# Ordered comparisons require values of the same type. +assert.fails(lambda: None < None, "not impl") +assert.fails(lambda: None < False, "not impl") +assert.fails(lambda: False < list, "not impl") +assert.fails(lambda: list < {}, "not impl") +assert.fails(lambda: {} < (lambda: None), "not impl") +assert.fails(lambda: (lambda: None) < 0, "not impl") +assert.fails(lambda: 0 < [], "not impl") +assert.fails(lambda: [] < "", "not impl") +assert.fails(lambda: "" < (), "not impl") +# Except int < float: +assert.lt(1, 2.0) +assert.lt(2.0, 3) + +--- +# cyclic data structures +load("assert.star", "assert") + +cyclic = [1, 2, 3] # list cycle +cyclic[1] = cyclic +assert.eq(str(cyclic), "[1, [...], 3]") +assert.fails(lambda: cyclic < cyclic, "maximum recursion") +assert.fails(lambda: cyclic == cyclic, "maximum recursion") +cyclic2 = [1, 2, 3] +cyclic2[1] = cyclic2 +assert.fails(lambda: cyclic2 == cyclic, "maximum recursion") + +cyclic3 = [1, [2, 3]] # list-list cycle +cyclic3[1][0] = cyclic3 +assert.eq(str(cyclic3), "[1, [[...], 3]]") +cyclic4 = {"x": 1} +cyclic4["x"] = cyclic4 +assert.eq(str(cyclic4), "{\"x\": {...}}") +cyclic5 = [0, {"x": 1}] # list-dict cycle +cyclic5[1]["x"] = cyclic5 +assert.eq(str(cyclic5), "[0, {\"x\": [...]}]") +assert.eq(str(cyclic5), "[0, {\"x\": [...]}]") +assert.fails(lambda: cyclic5 == cyclic5 ,"maximum recursion") +cyclic6 = [0, {"x": 1}] +cyclic6[1]["x"] = cyclic6 +assert.fails(lambda: cyclic5 == cyclic6, "maximum recursion") + +--- +# regression +load("assert.star", "assert") + +# was a parse error: +assert.eq(("ababab"[2:]).replace("b", "c"), "acac") +assert.eq("ababab"[2:].replace("b", "c"), "acac") + +# test parsing of line continuation, at toplevel and in expression. +three = 1 + \ + 2 +assert.eq(1 + \ + 2, three) + +--- +# A regression test for error position information. + +_ = {}.get(1, default=2) ### "get: unexpected keyword arguments" + +--- +# Load exposes explicitly declared globals from other modules. +load('assert.star', 'assert', 'freeze') +assert.eq(str(freeze), '<built-in function freeze>') + +--- +# Load does not expose pre-declared globals from other modules. +# See github.com/google/skylark/issues/75. +load('assert.star', 'assert', 'matches') ### "matches not found in module" + +--- +# Load does not expose universals accessible in other modules. +load('assert.star', 'len') ### "len not found in module" + + +--- +# Test plus folding optimization. +load('assert.star', 'assert') + +s = "s" +l = [4] +t = (4,) + +assert.eq("a" + "b" + "c", "abc") +assert.eq("a" + "b" + s + "c", "absc") +assert.eq(() + (1,) + (2, 3), (1, 2, 3)) +assert.eq(() + (1,) + t + (2, 3), (1, 4, 2, 3)) +assert.eq([] + [1] + [2, 3], [1, 2, 3]) +assert.eq([] + [1] + l + [2, 3], [1, 4, 2, 3]) + +assert.fails(lambda: "a" + "b" + 1 + "c", "unknown binary op: string \\+ int") +assert.fails(lambda: () + () + 1 + (), "unknown binary op: tuple \\+ int") +assert.fails(lambda: [] + [] + 1 + [], "unknown binary op: list \\+ int") + + + +--- +load('assert.star', 'froze') ### `name froze not found .*did you mean freeze` diff --git a/starlark/testdata/module.star b/starlark/testdata/module.star new file mode 100644 index 0000000..6aac2e2 --- /dev/null +++ b/starlark/testdata/module.star @@ -0,0 +1,17 @@ +# Tests of Module. + +load("assert.star", "assert") + +assert.eq(type(assert), "module") +assert.eq(str(assert), '<module "assert">') +assert.eq(dir(assert), ["contains", "eq", "fail", "fails", "lt", "ne", "true"]) +assert.fails(lambda : {assert: None}, "unhashable: module") + +def assignfield(): + assert.foo = None + +assert.fails(assignfield, "can't assign to .foo field of module") + +# no such field +assert.fails(lambda : assert.nonesuch, "module has no .nonesuch field or method$") +assert.fails(lambda : assert.falls, "module has no .falls field or method .did you mean .fails\\?") diff --git a/starlark/testdata/paths.star b/starlark/testdata/paths.star new file mode 100644 index 0000000..cf8a3c4 --- /dev/null +++ b/starlark/testdata/paths.star @@ -0,0 +1,250 @@ +# Copyright 2017 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Skylib module containing file path manipulation functions. + +NOTE: The functions in this module currently only support paths with Unix-style +path separators (forward slash, "/"); they do not handle Windows-style paths +with backslash separators or drive letters. +""" + +# This file is in the Bazel build language dialect of Starlark, +# so declarations of 'fail' and 'struct' are required to make +# it compile in the core language. +def fail(msg): + print(msg) + +struct = dict + +def _basename(p): + """Returns the basename (i.e., the file portion) of a path. + + Note that if `p` ends with a slash, this function returns an empty string. + This matches the behavior of Python's `os.path.basename`, but differs from + the Unix `basename` command (which would return the path segment preceding + the final slash). + + Args: + p: The path whose basename should be returned. + + Returns: + The basename of the path, which includes the extension. + """ + return p.rpartition("/")[-1] + +def _dirname(p): + """Returns the dirname of a path. + + The dirname is the portion of `p` up to but not including the file portion + (i.e., the basename). Any slashes immediately preceding the basename are not + included, unless omitting them would make the dirname empty. + + Args: + p: The path whose dirname should be returned. + + Returns: + The dirname of the path. + """ + prefix, sep, _ = p.rpartition("/") + if not prefix: + return sep + else: + # If there are multiple consecutive slashes, strip them all out as Python's + # os.path.dirname does. + return prefix.rstrip("/") + +def _is_absolute(path): + """Returns `True` if `path` is an absolute path. + + Args: + path: A path (which is a string). + + Returns: + `True` if `path` is an absolute path. + """ + return path.startswith("/") or (len(path) > 2 and path[1] == ":") + +def _join(path, *others): + """Joins one or more path components intelligently. + + This function mimics the behavior of Python's `os.path.join` function on POSIX + platform. It returns the concatenation of `path` and any members of `others`, + inserting directory separators before each component except the first. The + separator is not inserted if the path up until that point is either empty or + already ends in a separator. + + If any component is an absolute path, all previous components are discarded. + + Args: + path: A path segment. + *others: Additional path segments. + + Returns: + A string containing the joined paths. + """ + result = path + + for p in others: + if _is_absolute(p): + result = p + elif not result or result.endswith("/"): + result += p + else: + result += "/" + p + + return result + +def _normalize(path): + """Normalizes a path, eliminating double slashes and other redundant segments. + + This function mimics the behavior of Python's `os.path.normpath` function on + POSIX platforms; specifically: + + - If the entire path is empty, "." is returned. + - All "." segments are removed, unless the path consists solely of a single + "." segment. + - Trailing slashes are removed, unless the path consists solely of slashes. + - ".." segments are removed as long as there are corresponding segments + earlier in the path to remove; otherwise, they are retained as leading ".." + segments. + - Single and double leading slashes are preserved, but three or more leading + slashes are collapsed into a single leading slash. + - Multiple adjacent internal slashes are collapsed into a single slash. + + Args: + path: A path. + + Returns: + The normalized path. + """ + if not path: + return "." + + if path.startswith("//") and not path.startswith("///"): + initial_slashes = 2 + elif path.startswith("/"): + initial_slashes = 1 + else: + initial_slashes = 0 + is_relative = (initial_slashes == 0) + + components = path.split("/") + new_components = [] + + for component in components: + if component in ("", "."): + continue + if component == "..": + if new_components and new_components[-1] != "..": + # Only pop the last segment if it isn't another "..". + new_components.pop() + elif is_relative: + # Preserve leading ".." segments for relative paths. + new_components.append(component) + else: + new_components.append(component) + + path = "/".join(new_components) + if not is_relative: + path = ("/" * initial_slashes) + path + + return path or "." + +def _relativize(path, start): + """Returns the portion of `path` that is relative to `start`. + + Because we do not have access to the underlying file system, this + implementation differs slightly from Python's `os.path.relpath` in that it + will fail if `path` is not beneath `start` (rather than use parent segments to + walk up to the common file system root). + + Relativizing paths that start with parent directory references only works if + the path both start with the same initial parent references. + + Args: + path: The path to relativize. + start: The ancestor path against which to relativize. + + Returns: + The portion of `path` that is relative to `start`. + """ + segments = _normalize(path).split("/") + start_segments = _normalize(start).split("/") + if start_segments == ["."]: + start_segments = [] + start_length = len(start_segments) + + if (path.startswith("/") != start.startswith("/") or + len(segments) < start_length): + fail("Path '%s' is not beneath '%s'" % (path, start)) + + for ancestor_segment, segment in zip(start_segments, segments): + if ancestor_segment != segment: + fail("Path '%s' is not beneath '%s'" % (path, start)) + + length = len(segments) - start_length + result_segments = segments[-length:] + return "/".join(result_segments) + +def _replace_extension(p, new_extension): + """Replaces the extension of the file at the end of a path. + + If the path has no extension, the new extension is added to it. + + Args: + p: The path whose extension should be replaced. + new_extension: The new extension for the file. The new extension should + begin with a dot if you want the new filename to have one. + + Returns: + The path with the extension replaced (or added, if it did not have one). + """ + return _split_extension(p)[0] + new_extension + +def _split_extension(p): + """Splits the path `p` into a tuple containing the root and extension. + + Leading periods on the basename are ignored, so + `path.split_extension(".bashrc")` returns `(".bashrc", "")`. + + Args: + p: The path whose root and extension should be split. + + Returns: + A tuple `(root, ext)` such that the root is the path without the file + extension, and `ext` is the file extension (which, if non-empty, contains + the leading dot). The returned tuple always satisfies the relationship + `root + ext == p`. + """ + b = _basename(p) + last_dot_in_basename = b.rfind(".") + + # If there is no dot or the only dot in the basename is at the front, then + # there is no extension. + if last_dot_in_basename <= 0: + return (p, "") + + dot_distance_from_end = len(b) - last_dot_in_basename + return (p[:-dot_distance_from_end], p[-dot_distance_from_end:]) + +paths = struct( + basename = _basename, + dirname = _dirname, + is_absolute = _is_absolute, + join = _join, + normalize = _normalize, + relativize = _relativize, + replace_extension = _replace_extension, + split_extension = _split_extension, +) diff --git a/starlark/testdata/recursion.star b/starlark/testdata/recursion.star new file mode 100644 index 0000000..3368614 --- /dev/null +++ b/starlark/testdata/recursion.star @@ -0,0 +1,43 @@ +# Tests of Starlark recursion and while statement. + +# This is a "chunked" file: each "---" effectively starts a new file. + +# option:recursion + +load("assert.star", "assert") + +def sum(n): + r = 0 + while n > 0: + r += n + n -= 1 + return r + +def fib(n): + if n <= 1: + return 1 + return fib(n-1) + fib(n-2) + +def while_break(n): + r = 0 + while n > 0: + if n == 5: + break + r += n + n -= 1 + return r + +def while_continue(n): + r = 0 + while n > 0: + if n % 2 == 0: + n -= 1 + continue + r += n + n -= 1 + return r + +assert.eq(fib(5), 8) +assert.eq(sum(5), 5+4+3+2+1) +assert.eq(while_break(10), 40) +assert.eq(while_continue(10), 25) diff --git a/starlark/testdata/set.star b/starlark/testdata/set.star new file mode 100644 index 0000000..bca4144 --- /dev/null +++ b/starlark/testdata/set.star @@ -0,0 +1,118 @@ +# Tests of Starlark 'set' +# option:set + +# Sets are not a standard part of Starlark, so the features +# tested in this file must be enabled in the application by setting +# resolve.AllowSet. (All sets are created by calls to the 'set' +# built-in or derived from operations on existing sets.) +# The semantics are subject to change as the spec evolves. + +# TODO(adonovan): support set mutation: +# - del set[k] +# - set.remove +# - set.update +# - set.clear +# - set += iterable, perhaps? +# Test iterator invalidation. + +load("assert.star", "assert") + +# literals +# Parser does not currently support {1, 2, 3}. +# TODO(adonovan): add test to syntax/testdata/errors.star. + +# set comprehensions +# Parser does not currently support {x for x in y}. +# See syntax/testdata/errors.star. + +# set constructor +assert.eq(type(set()), "set") +assert.eq(list(set()), []) +assert.eq(type(set([1, 3, 2, 3])), "set") +assert.eq(list(set([1, 3, 2, 3])), [1, 3, 2]) +assert.eq(type(set("hello".elems())), "set") +assert.eq(list(set("hello".elems())), ["h", "e", "l", "o"]) +assert.eq(list(set(range(3))), [0, 1, 2]) +assert.fails(lambda : set(1), "got int, want iterable") +assert.fails(lambda : set(1, 2, 3), "got 3 arguments") +assert.fails(lambda : set([1, 2, {}]), "unhashable type: dict") + +# truth +assert.true(not set()) +assert.true(set([False])) +assert.true(set([1, 2, 3])) + +x = set([1, 2, 3]) +y = set([3, 4, 5]) + +# set + any is not defined +assert.fails(lambda : x + y, "unknown.*: set \\+ set") + +# set | set (use resolve.AllowBitwise to enable it) +assert.eq(list(set("a".elems()) | set("b".elems())), ["a", "b"]) +assert.eq(list(set("ab".elems()) | set("bc".elems())), ["a", "b", "c"]) +assert.fails(lambda : set() | [], "unknown binary op: set | list") +assert.eq(type(x | y), "set") +assert.eq(list(x | y), [1, 2, 3, 4, 5]) +assert.eq(list(x | set([5, 1])), [1, 2, 3, 5]) +assert.eq(list(x | set((6, 5, 4))), [1, 2, 3, 6, 5, 4]) + +# set.union (allows any iterable for right operand) +assert.eq(list(set("a".elems()).union("b".elems())), ["a", "b"]) +assert.eq(list(set("ab".elems()).union("bc".elems())), ["a", "b", "c"]) +assert.eq(set().union([]), set()) +assert.eq(type(x.union(y)), "set") +assert.eq(list(x.union(y)), [1, 2, 3, 4, 5]) +assert.eq(list(x.union([5, 1])), [1, 2, 3, 5]) +assert.eq(list(x.union((6, 5, 4))), [1, 2, 3, 6, 5, 4]) +assert.fails(lambda : x.union([1, 2, {}]), "unhashable type: dict") + +# intersection, set & set (use resolve.AllowBitwise to enable it) +assert.eq(list(set("a".elems()) & set("b".elems())), []) +assert.eq(list(set("ab".elems()) & set("bc".elems())), ["b"]) + +# symmetric difference, set ^ set (use resolve.AllowBitwise to enable it) +assert.eq(set([1, 2, 3]) ^ set([4, 5, 3]), set([1, 2, 4, 5])) + +def test_set_augmented_assign(): + x = set([1, 2, 3]) + x &= set([2, 3]) + assert.eq(x, set([2, 3])) + x |= set([1]) + assert.eq(x, set([1, 2, 3])) + x ^= set([4, 5, 3]) + assert.eq(x, set([1, 2, 4, 5])) + +test_set_augmented_assign() + +# len +assert.eq(len(x), 3) +assert.eq(len(y), 3) +assert.eq(len(x | y), 5) + +# str +assert.eq(str(set([1])), "set([1])") +assert.eq(str(set([2, 3])), "set([2, 3])") +assert.eq(str(set([3, 2])), "set([3, 2])") + +# comparison +assert.eq(x, x) +assert.eq(y, y) +assert.true(x != y) +assert.eq(set([1, 2, 3]), set([3, 2, 1])) +assert.fails(lambda : x < y, "set < set not implemented") + +# iteration +assert.true(type([elem for elem in x]), "list") +assert.true(list([elem for elem in x]), [1, 2, 3]) + +def iter(): + list = [] + for elem in x: + list.append(elem) + return list + +assert.eq(iter(), [1, 2, 3]) + +# sets are not indexable +assert.fails(lambda : x[0], "unhandled.*operation") diff --git a/starlark/testdata/string.star b/starlark/testdata/string.star new file mode 100644 index 0000000..b317d1a --- /dev/null +++ b/starlark/testdata/string.star @@ -0,0 +1,472 @@ +# Tests of Starlark 'string' +# option:set + +load("assert.star", "assert") + +# raw string literals: +assert.eq(r"a\bc", "a\\bc") + +# truth +assert.true("abc") +assert.true(chr(0)) +assert.true(not "") + +# str + str +assert.eq("a" + "b" + "c", "abc") + +# str * int, int * str +assert.eq("abc" * 0, "") +assert.eq("abc" * -1, "") +assert.eq("abc" * 1, "abc") +assert.eq("abc" * 5, "abcabcabcabcabc") +assert.eq(0 * "abc", "") +assert.eq(-1 * "abc", "") +assert.eq(1 * "abc", "abc") +assert.eq(5 * "abc", "abcabcabcabcabc") +assert.fails(lambda: 1.0 * "abc", "unknown.*float \\* str") +assert.fails(lambda: "abc" * (1000000 * 1000000), "repeat count 1000000000000 too large") +assert.fails(lambda: "abc" * 1000000 * 1000000, "excessive repeat \\(3000000 \\* 1000000 elements") + +# len +assert.eq(len("Hello, 世界!"), 14) +assert.eq(len("𐐷"), 4) # U+10437 has a 4-byte UTF-8 encoding (and a 2-code UTF-16 encoding) + +# chr & ord +assert.eq(chr(65), "A") # 1-byte UTF-8 encoding +assert.eq(chr(1049), "Й") # 2-byte UTF-8 encoding +assert.eq(chr(0x1F63F), "😿") # 4-byte UTF-8 encoding +assert.fails(lambda: chr(-1), "Unicode code point -1 out of range \\(<0\\)") +assert.fails(lambda: chr(0x110000), "Unicode code point U\\+110000 out of range \\(>0x10FFFF\\)") +assert.eq(ord("A"), 0x41) +assert.eq(ord("Й"), 0x419) +assert.eq(ord("世"), 0x4e16) +assert.eq(ord("😿"), 0x1F63F) +assert.eq(ord("Й"[1:]), 0xFFFD) # = Unicode replacement character +assert.fails(lambda: ord("abc"), "string encodes 3 Unicode code points, want 1") +assert.fails(lambda: ord(""), "string encodes 0 Unicode code points, want 1") +assert.fails(lambda: ord("😿"[1:]), "string encodes 3 Unicode code points, want 1") # 3 x 0xFFFD + +# string.codepoint_ords +assert.eq(type("abcЙ😿".codepoint_ords()), "string.codepoints") +assert.eq(str("abcЙ😿".codepoint_ords()), '"abcЙ😿".codepoint_ords()') +assert.eq(list("abcЙ😿".codepoint_ords()), [97, 98, 99, 1049, 128575]) +assert.eq(list(("A" + "😿Z"[1:]).codepoint_ords()), [ord("A"), 0xFFFD, 0xFFFD, 0xFFFD, ord("Z")]) +assert.eq(list("".codepoint_ords()), []) +assert.fails(lambda: "abcЙ😿".codepoint_ords()[2], "unhandled index") # not indexable +assert.fails(lambda: len("abcЙ😿".codepoint_ords()), "no len") # unknown length + +# string.codepoints +assert.eq(type("abcЙ😿".codepoints()), "string.codepoints") +assert.eq(str("abcЙ😿".codepoints()), '"abcЙ😿".codepoints()') +assert.eq(list("abcЙ😿".codepoints()), ["a", "b", "c", "Й", "😿"]) +assert.eq(list(("A" + "😿Z"[1:]).codepoints()), ["A", "�", "�", "�", "Z"]) +assert.eq(list("".codepoints()), []) +assert.fails(lambda: "abcЙ😿".codepoints()[2], "unhandled index") # not indexable +assert.fails(lambda: len("abcЙ😿".codepoints()), "no len") # unknown length + +# string.elem_ords +assert.eq(type("abcЙ😿".elem_ords()), "string.elems") +assert.eq(str("abcЙ😿".elem_ords()), '"abcЙ😿".elem_ords()') +assert.eq(list("abcЙ😿".elem_ords()), [97, 98, 99, 208, 153, 240, 159, 152, 191]) +assert.eq(list(("A" + "😿Z"[1:]).elem_ords()), [65, 159, 152, 191, 90]) +assert.eq(list("".elem_ords()), []) +assert.eq("abcЙ😿".elem_ords()[2], 99) # indexable +assert.eq(len("abcЙ😿".elem_ords()), 9) # known length + +# string.elems (1-byte substrings, which are invalid text) +assert.eq(type("abcЙ😿".elems()), "string.elems") +assert.eq(str("abcЙ😿".elems()), '"abcЙ😿".elems()') +assert.eq( + repr(list("abcЙ😿".elems())), + r'["a", "b", "c", "\xd0", "\x99", "\xf0", "\x9f", "\x98", "\xbf"]', +) +assert.eq( + repr(list(("A" + "😿Z"[1:]).elems())), + r'["A", "\x9f", "\x98", "\xbf", "Z"]', +) +assert.eq(list("".elems()), []) +assert.eq("abcЙ😿".elems()[2], "c") # indexable +assert.eq(len("abcЙ😿".elems()), 9) # known length + +# indexing, x[i] +assert.eq("Hello, 世界!"[0], "H") +assert.eq(repr("Hello, 世界!"[7]), r'"\xe4"') # (invalid text) +assert.eq("Hello, 世界!"[13], "!") +assert.fails(lambda: "abc"[-4], "out of range") +assert.eq("abc"[-3], "a") +assert.eq("abc"[-2], "b") +assert.eq("abc"[-1], "c") +assert.eq("abc"[0], "a") +assert.eq("abc"[1], "b") +assert.eq("abc"[2], "c") +assert.fails(lambda: "abc"[4], "out of range") + +# x[i] = ... +def f(): + "abc"[1] = "B" + +assert.fails(f, "string.*does not support.*assignment") + +# slicing, x[i:j] +assert.eq("abc"[:], "abc") +assert.eq("abc"[-4:], "abc") +assert.eq("abc"[-3:], "abc") +assert.eq("abc"[-2:], "bc") +assert.eq("abc"[-1:], "c") +assert.eq("abc"[0:], "abc") +assert.eq("abc"[1:], "bc") +assert.eq("abc"[2:], "c") +assert.eq("abc"[3:], "") +assert.eq("abc"[4:], "") +assert.eq("abc"[:-4], "") +assert.eq("abc"[:-3], "") +assert.eq("abc"[:-2], "a") +assert.eq("abc"[:-1], "ab") +assert.eq("abc"[:0], "") +assert.eq("abc"[:1], "a") +assert.eq("abc"[:2], "ab") +assert.eq("abc"[:3], "abc") +assert.eq("abc"[:4], "abc") +assert.eq("abc"[1:2], "b") +assert.eq("abc"[2:1], "") +assert.eq(repr("😿"[:1]), r'"\xf0"') # (invalid text) + +# non-unit strides +assert.eq("abcd"[0:4:1], "abcd") +assert.eq("abcd"[::2], "ac") +assert.eq("abcd"[1::2], "bd") +assert.eq("abcd"[4:0:-1], "dcb") +assert.eq("banana"[7::-2], "aaa") +assert.eq("banana"[6::-2], "aaa") +assert.eq("banana"[5::-2], "aaa") +assert.eq("banana"[4::-2], "nnb") +assert.eq("banana"[::-1], "ananab") +assert.eq("banana"[None:None:-2], "aaa") +assert.fails(lambda: "banana"[1.0::], "invalid start index: got float, want int") +assert.fails(lambda: "banana"[:"":], "invalid end index: got string, want int") +assert.fails(lambda: "banana"[:"":True], "invalid slice step: got bool, want int") + +# in, not in +assert.true("oo" in "food") +assert.true("ox" not in "food") +assert.true("" in "food") +assert.true("" in "") +assert.fails(lambda: 1 in "", "requires string as left operand") +assert.fails(lambda: "" in 1, "unknown binary op: string in int") + +# ==, != +assert.eq("hello", "he" + "llo") +assert.ne("hello", "Hello") + +# hash must follow java.lang.String.hashCode. +wanthash = { + "": 0, + "\0" * 100: 0, + "hello": 99162322, + "world": 113318802, + "Hello, 世界!": 417292677, +} +gothash = {s: hash(s) for s in wanthash} +assert.eq(gothash, wanthash) + +# TODO(adonovan): ordered comparisons + +# string % tuple formatting +assert.eq("A %d %x Z" % (123, 456), "A 123 1c8 Z") +assert.eq("A %(foo)d %(bar)s Z" % {"foo": 123, "bar": "hi"}, "A 123 hi Z") +assert.eq("%s %r" % ("hi", "hi"), 'hi "hi"') # TODO(adonovan): use ''-quotation +assert.eq("%%d %d" % 1, "%d 1") +assert.fails(lambda: "%d %d" % 1, "not enough arguments for format string") +assert.fails(lambda: "%d %d" % (1, 2, 3), "too many arguments for format string") +assert.fails(lambda: "" % 1, "too many arguments for format string") + +# %c +assert.eq("%c" % 65, "A") +assert.eq("%c" % 0x3b1, "α") +assert.eq("%c" % "A", "A") +assert.eq("%c" % "α", "α") +assert.fails(lambda: "%c" % "abc", "requires a single-character string") +assert.fails(lambda: "%c" % "", "requires a single-character string") +assert.fails(lambda: "%c" % 65.0, "requires int or single-character string") +assert.fails(lambda: "%c" % 10000000, "requires a valid Unicode code point") +assert.fails(lambda: "%c" % -1, "requires a valid Unicode code point") +# TODO(adonovan): more tests + +# str.format +assert.eq("a{}b".format(123), "a123b") +assert.eq("a{}b{}c{}d{}".format(1, 2, 3, 4), "a1b2c3d4") +assert.eq("a{{b".format(), "a{b") +assert.eq("a}}b".format(), "a}b") +assert.eq("a{{b}}c".format(), "a{b}c") +assert.eq("a{x}b{y}c{}".format(1, x = 2, y = 3), "a2b3c1") +assert.fails(lambda: "a{z}b".format(x = 1), "keyword z not found") +assert.fails(lambda: "{-1}".format(1), "keyword -1 not found") +assert.fails(lambda: "{-0}".format(1), "keyword -0 not found") +assert.fails(lambda: "{+0}".format(1), "keyword \\+0 not found") +assert.fails(lambda: "{+1}".format(1), "keyword \\+1 not found") # starlark-go/issues/114 +assert.eq("{0000000000001}".format(0, 1), "1") +assert.eq("{012}".format(*range(100)), "12") # decimal, despite leading zeros +assert.fails(lambda: "{0,1} and {1}".format(1, 2), "keyword 0,1 not found") +assert.fails(lambda: "a{123}b".format(), "tuple index out of range") +assert.fails(lambda: "a{}b{}c".format(1), "tuple index out of range") +assert.eq("a{010}b".format(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10), "a10b") # index is decimal +assert.fails(lambda: "a{}b{1}c".format(1, 2), "cannot switch from automatic field numbering to manual") +assert.eq("a{!s}c".format("b"), "abc") +assert.eq("a{!r}c".format("b"), r'a"b"c') +assert.eq("a{x!r}c".format(x = "b"), r'a"b"c') +assert.fails(lambda: "{x!}".format(x = 1), "unknown conversion") +assert.fails(lambda: "{x!:}".format(x = 1), "unknown conversion") +assert.fails(lambda: "{a.b}".format(1), "syntax x.y is not supported") +assert.fails(lambda: "{a[0]}".format(1), "syntax a\\[i\\] is not supported") +assert.fails(lambda: "{ {} }".format(1), "nested replacement fields not supported") +assert.fails(lambda: "{{}".format(1), "single '}' in format") +assert.fails(lambda: "{}}".format(1), "single '}' in format") +assert.fails(lambda: "}}{".format(1), "unmatched '{' in format") +assert.fails(lambda: "}{{".format(1), "single '}' in format") + +# str.split, str.rsplit +assert.eq("a.b.c.d".split("."), ["a", "b", "c", "d"]) +assert.eq("a.b.c.d".rsplit("."), ["a", "b", "c", "d"]) +assert.eq("a.b.c.d".split(".", -1), ["a", "b", "c", "d"]) +assert.eq("a.b.c.d".rsplit(".", -1), ["a", "b", "c", "d"]) +assert.eq("a.b.c.d".split(".", 0), ["a.b.c.d"]) +assert.eq("a.b.c.d".rsplit(".", 0), ["a.b.c.d"]) +assert.eq("a.b.c.d".split(".", 1), ["a", "b.c.d"]) +assert.eq("a.b.c.d".rsplit(".", 1), ["a.b.c", "d"]) +assert.eq("a.b.c.d".split(".", 2), ["a", "b", "c.d"]) +assert.eq("a.b.c.d".rsplit(".", 2), ["a.b", "c", "d"]) +assert.eq(" ".split("."), [" "]) +assert.eq(" ".rsplit("."), [" "]) + +# {,r}split on white space: +assert.eq(" a bc\n def \t ghi".split(), ["a", "bc", "def", "ghi"]) +assert.eq(" a bc\n def \t ghi".split(None), ["a", "bc", "def", "ghi"]) +assert.eq(" a bc\n def \t ghi".split(None, 0), ["a bc\n def \t ghi"]) +assert.eq(" a bc\n def \t ghi".rsplit(None, 0), [" a bc\n def \t ghi"]) +assert.eq(" a bc\n def \t ghi".split(None, 1), ["a", "bc\n def \t ghi"]) +assert.eq(" a bc\n def \t ghi".rsplit(None, 1), [" a bc\n def", "ghi"]) +assert.eq(" a bc\n def \t ghi".split(None, 2), ["a", "bc", "def \t ghi"]) +assert.eq(" a bc\n def \t ghi".rsplit(None, 2), [" a bc", "def", "ghi"]) +assert.eq(" a bc\n def \t ghi".split(None, 3), ["a", "bc", "def", "ghi"]) +assert.eq(" a bc\n def \t ghi".rsplit(None, 3), [" a", "bc", "def", "ghi"]) +assert.eq(" a bc\n def \t ghi".split(None, 4), ["a", "bc", "def", "ghi"]) +assert.eq(" a bc\n def \t ghi".rsplit(None, 4), ["a", "bc", "def", "ghi"]) +assert.eq(" a bc\n def \t ghi".rsplit(None, 5), ["a", "bc", "def", "ghi"]) + +assert.eq(" a bc\n def \t ghi ".split(None, 0), ["a bc\n def \t ghi "]) +assert.eq(" a bc\n def \t ghi ".rsplit(None, 0), [" a bc\n def \t ghi"]) +assert.eq(" a bc\n def \t ghi ".split(None, 1), ["a", "bc\n def \t ghi "]) +assert.eq(" a bc\n def \t ghi ".rsplit(None, 1), [" a bc\n def", "ghi"]) + +# Observe the algorithmic difference when splitting on spaces versus other delimiters. +assert.eq("--aa--bb--cc--".split("-", 0), ["--aa--bb--cc--"]) # contrast this +assert.eq(" aa bb cc ".split(None, 0), ["aa bb cc "]) # with this +assert.eq("--aa--bb--cc--".rsplit("-", 0), ["--aa--bb--cc--"]) # ditto this +assert.eq(" aa bb cc ".rsplit(None, 0), [" aa bb cc"]) # and this + +# +assert.eq("--aa--bb--cc--".split("-", 1), ["", "-aa--bb--cc--"]) +assert.eq("--aa--bb--cc--".rsplit("-", 1), ["--aa--bb--cc-", ""]) +assert.eq(" aa bb cc ".split(None, 1), ["aa", "bb cc "]) +assert.eq(" aa bb cc ".rsplit(None, 1), [" aa bb", "cc"]) + +# +assert.eq("--aa--bb--cc--".split("-", -1), ["", "", "aa", "", "bb", "", "cc", "", ""]) +assert.eq("--aa--bb--cc--".rsplit("-", -1), ["", "", "aa", "", "bb", "", "cc", "", ""]) +assert.eq(" aa bb cc ".split(None, -1), ["aa", "bb", "cc"]) +assert.eq(" aa bb cc ".rsplit(None, -1), ["aa", "bb", "cc"]) +assert.eq(" ".split(None), []) +assert.eq(" ".rsplit(None), []) + +assert.eq("localhost:80".rsplit(":", 1)[-1], "80") + +# str.splitlines +assert.eq("\nabc\ndef".splitlines(), ["", "abc", "def"]) +assert.eq("\nabc\ndef".splitlines(True), ["\n", "abc\n", "def"]) +assert.eq("\nabc\ndef\n".splitlines(), ["", "abc", "def"]) +assert.eq("\nabc\ndef\n".splitlines(True), ["\n", "abc\n", "def\n"]) +assert.eq("".splitlines(), []) # +assert.eq("".splitlines(True), []) # +assert.eq("a".splitlines(), ["a"]) +assert.eq("a".splitlines(True), ["a"]) +assert.eq("\n".splitlines(), [""]) +assert.eq("\n".splitlines(True), ["\n"]) +assert.eq("a\n".splitlines(), ["a"]) +assert.eq("a\n".splitlines(True), ["a\n"]) +assert.eq("a\n\nb".splitlines(), ["a", "", "b"]) +assert.eq("a\n\nb".splitlines(True), ["a\n", "\n", "b"]) +assert.eq("a\nb\nc".splitlines(), ["a", "b", "c"]) +assert.eq("a\nb\nc".splitlines(True), ["a\n", "b\n", "c"]) +assert.eq("a\nb\nc\n".splitlines(), ["a", "b", "c"]) +assert.eq("a\nb\nc\n".splitlines(True), ["a\n", "b\n", "c\n"]) + +# str.{,l,r}strip +assert.eq(" \tfoo\n ".strip(), "foo") +assert.eq(" \tfoo\n ".lstrip(), "foo\n ") +assert.eq(" \tfoo\n ".rstrip(), " \tfoo") +assert.eq(" \tfoo\n ".strip(""), "foo") +assert.eq(" \tfoo\n ".lstrip(""), "foo\n ") +assert.eq(" \tfoo\n ".rstrip(""), " \tfoo") +assert.eq("blah.h".strip("b.h"), "la") +assert.eq("blah.h".lstrip("b.h"), "lah.h") +assert.eq("blah.h".rstrip("b.h"), "bla") + +# str.count +assert.eq("banana".count("a"), 3) +assert.eq("banana".count("a", 2), 2) +assert.eq("banana".count("a", -4, -2), 1) +assert.eq("banana".count("a", 1, 4), 2) +assert.eq("banana".count("a", 0, -100), 0) + +# str.{starts,ends}with +assert.true("foo".endswith("oo")) +assert.true(not "foo".endswith("x")) +assert.true("foo".startswith("fo")) +assert.true(not "foo".startswith("x")) +assert.fails(lambda: "foo".startswith(1), "got int.*want string") + +# +assert.true("abc".startswith(("a", "A"))) +assert.true("ABC".startswith(("a", "A"))) +assert.true(not "ABC".startswith(("b", "B"))) +assert.fails(lambda: "123".startswith((1, 2)), "got int, for element 0") +assert.fails(lambda: "123".startswith(["3"]), "got list") + +# +assert.true("abc".endswith(("c", "C"))) +assert.true("ABC".endswith(("c", "C"))) +assert.true(not "ABC".endswith(("b", "B"))) +assert.fails(lambda: "123".endswith((1, 2)), "got int, for element 0") +assert.fails(lambda: "123".endswith(["3"]), "got list") + +# start/end +assert.true("abc".startswith("bc", 1)) +assert.true(not "abc".startswith("b", 999)) +assert.true("abc".endswith("ab", None, -1)) +assert.true(not "abc".endswith("b", None, -999)) + +# str.replace +assert.eq("banana".replace("a", "o", 1), "bonana") +assert.eq("banana".replace("a", "o"), "bonono") +# TODO(adonovan): more tests + +# str.{,r}find +assert.eq("foofoo".find("oo"), 1) +assert.eq("foofoo".find("ox"), -1) +assert.eq("foofoo".find("oo", 2), 4) +assert.eq("foofoo".rfind("oo"), 4) +assert.eq("foofoo".rfind("ox"), -1) +assert.eq("foofoo".rfind("oo", 1, 4), 1) +assert.eq("foofoo".find(""), 0) +assert.eq("foofoo".rfind(""), 6) + +# str.{,r}partition +assert.eq("foo/bar/wiz".partition("/"), ("foo", "/", "bar/wiz")) +assert.eq("foo/bar/wiz".rpartition("/"), ("foo/bar", "/", "wiz")) +assert.eq("foo/bar/wiz".partition("."), ("foo/bar/wiz", "", "")) +assert.eq("foo/bar/wiz".rpartition("."), ("", "", "foo/bar/wiz")) +assert.fails(lambda: "foo/bar/wiz".partition(""), "empty separator") +assert.fails(lambda: "foo/bar/wiz".rpartition(""), "empty separator") + +assert.eq("?".join(["foo", "a/b/c.go".rpartition("/")[0]]), "foo?a/b") + +# str.is{alpha,...} +def test_predicates(): + predicates = ["alnum", "alpha", "digit", "lower", "space", "title", "upper"] + table = { + "Hello, World!": "title", + "hello, world!": "lower", + "base64": "alnum lower", + "HAL-9000": "upper", + "Catch-22": "title", + "": "", + "\n\t\r": "space", + "abc": "alnum alpha lower", + "ABC": "alnum alpha upper", + "123": "alnum digit", + "DŽLJ": "alnum alpha upper", + "DžLj": "alnum alpha", + "Dž Lj": "title", + "džlj": "alnum alpha lower", + } + for str, want in table.items(): + got = " ".join([name for name in predicates if getattr(str, "is" + name)()]) + if got != want: + assert.fail("%r matched [%s], want [%s]" % (str, got, want)) + +test_predicates() + +# Strings are not iterable. +# ok +assert.eq(len("abc"), 3) # len +assert.true("a" in "abc") # str in str +assert.eq("abc"[1], "b") # indexing + +# not ok +def for_string(): + for x in "abc": + pass + +def args(*args): + return args + +assert.fails(lambda: args(*"abc"), "must be iterable, not string") # varargs +assert.fails(lambda: list("abc"), "got string, want iterable") # list(str) +assert.fails(lambda: tuple("abc"), "got string, want iterable") # tuple(str) +assert.fails(lambda: set("abc"), "got string, want iterable") # set(str) +assert.fails(lambda: set() | "abc", "unknown binary op: set | string") # set union +assert.fails(lambda: enumerate("ab"), "got string, want iterable") # enumerate +assert.fails(lambda: sorted("abc"), "got string, want iterable") # sorted +assert.fails(lambda: [].extend("bc"), "got string, want iterable") # list.extend +assert.fails(lambda: ",".join("abc"), "got string, want iterable") # string.join +assert.fails(lambda: dict(["ab"]), "not iterable .*string") # dict +assert.fails(for_string, "string value is not iterable") # for loop +assert.fails(lambda: [x for x in "abc"], "string value is not iterable") # comprehension +assert.fails(lambda: all("abc"), "got string, want iterable") # all +assert.fails(lambda: any("abc"), "got string, want iterable") # any +assert.fails(lambda: reversed("abc"), "got string, want iterable") # reversed +assert.fails(lambda: zip("ab", "cd"), "not iterable: string") # zip + +# str.join +assert.eq(",".join([]), "") +assert.eq(",".join(["a"]), "a") +assert.eq(",".join(["a", "b"]), "a,b") +assert.eq(",".join(["a", "b", "c"]), "a,b,c") +assert.eq(",".join(("a", "b", "c")), "a,b,c") +assert.eq("".join(("a", "b", "c")), "abc") +assert.fails(lambda: "".join(None), "got NoneType, want iterable") +assert.fails(lambda: "".join(["one", 2]), "join: in list, want string, got int") + +# TODO(adonovan): tests for: {,r}index + +# str.capitalize +assert.eq("hElLo, WoRlD!".capitalize(), "Hello, world!") +assert.eq("por qué".capitalize(), "Por qué") +assert.eq("¿Por qué?".capitalize(), "¿por qué?") + +# str.lower +assert.eq("hElLo, WoRlD!".lower(), "hello, world!") +assert.eq("por qué".lower(), "por qué") +assert.eq("¿Por qué?".lower(), "¿por qué?") +assert.eq("LJUBOVIĆ".lower(), "ljubović") +assert.true("dženan ljubović".islower()) + +# str.upper +assert.eq("hElLo, WoRlD!".upper(), "HELLO, WORLD!") +assert.eq("por qué".upper(), "POR QUÉ") +assert.eq("¿Por qué?".upper(), "¿POR QUÉ?") +assert.eq("ljubović".upper(), "LJUBOVIĆ") +assert.true("DŽENAN LJUBOVIĆ".isupper()) + +# str.title +assert.eq("hElLo, WoRlD!".title(), "Hello, World!") +assert.eq("por qué".title(), "Por Qué") +assert.eq("¿Por qué?".title(), "¿Por Qué?") +assert.eq("ljubović".title(), "Ljubović") +assert.true("Dženan Ljubović".istitle()) +assert.true(not "DŽenan LJubović".istitle()) + +# method spell check +assert.fails(lambda: "".starts_with, "no .starts_with field.*did you mean .startswith") +assert.fails(lambda: "".StartsWith, "no .StartsWith field.*did you mean .startswith") +assert.fails(lambda: "".fin, "no .fin field.*.did you mean .find") diff --git a/starlark/testdata/tuple.star b/starlark/testdata/tuple.star new file mode 100644 index 0000000..f306133 --- /dev/null +++ b/starlark/testdata/tuple.star @@ -0,0 +1,55 @@ +# Tests of Starlark 'tuple' + +load("assert.star", "assert") + +# literal +assert.eq((), ()) +assert.eq((1), 1) +assert.eq((1,), (1,)) +assert.ne((1), (1,)) +assert.eq((1, 2), (1, 2)) +assert.eq((1, 2, 3, 4, 5), (1, 2, 3, 4, 5)) +assert.ne((1, 2, 3), (1, 2, 4)) + +# truth +assert.true((False,)) +assert.true((False, False)) +assert.true(not ()) + +# indexing, x[i] +assert.eq(("a", "b")[0], "a") +assert.eq(("a", "b")[1], "b") + +# slicing, x[i:j] +assert.eq("abcd"[0:4:1], "abcd") +assert.eq("abcd"[::2], "ac") +assert.eq("abcd"[1::2], "bd") +assert.eq("abcd"[4:0:-1], "dcb") +banana = tuple("banana".elems()) +assert.eq(banana[7::-2], tuple("aaa".elems())) +assert.eq(banana[6::-2], tuple("aaa".elems())) +assert.eq(banana[5::-2], tuple("aaa".elems())) +assert.eq(banana[4::-2], tuple("nnb".elems())) + +# tuple +assert.eq(tuple(), ()) +assert.eq(tuple("abc".elems()), ("a", "b", "c")) +assert.eq(tuple(["a", "b", "c"]), ("a", "b", "c")) +assert.eq(tuple([1]), (1,)) +assert.fails(lambda: tuple(1), "got int, want iterable") + +# tuple * int, int * tuple +abc = tuple("abc".elems()) +assert.eq(abc * 0, ()) +assert.eq(abc * -1, ()) +assert.eq(abc * 1, abc) +assert.eq(abc * 3, ("a", "b", "c", "a", "b", "c", "a", "b", "c")) +assert.eq(0 * abc, ()) +assert.eq(-1 * abc, ()) +assert.eq(1 * abc, abc) +assert.eq(3 * abc, ("a", "b", "c", "a", "b", "c", "a", "b", "c")) +assert.fails(lambda: abc * (1000000 * 1000000), "repeat count 1000000000000 too large") +assert.fails(lambda: abc * 1000000 * 1000000, "excessive repeat \\(3000000 \\* 1000000 elements") + +# TODO(adonovan): test use of tuple as sequence +# (for loop, comprehension, library functions). |