-
Notifications
You must be signed in to change notification settings - Fork 6
/
build-tests.py
executable file
·303 lines (276 loc) · 11.7 KB
/
build-tests.py
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
#!/usr/bin/env python2
# tz.js - Library for working with timezones in JavaScript
# Written in 2011 by L. David Baron <[email protected]>
# To the extent possible under law, the author(s) have dedicated all
# copyright and related and neighboring rights to this software to the
# public domain worldwide. This software is distributed without any
# warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication
# along with this software. If not, see
# <http://creativecommons.org/publicdomain/zero/1.0/>.
# This script converts the compiled time zone data from the Olson time
# zone database (http://www.twinsun.com/tz/tz-link.htm), which is
# generated from the tzdata* data using the tzcode* code, and
# distributed on many Unix-ish operating systems in /usr/share/zoneinfo,
# into a JSON format suitable for inclusion in the tz.js JavaScript
# library.
import sys
import imp
import os.path
import subprocess
import re
import math
import tempfile
from optparse import OptionParser
__all__ = [
"output_tests"
]
START_YEAR = 1970
STOP_YEAR = 2050
STOP_SECS = 2524608000 # 2050, in seconds since the epoch
os.environ["LC_ALL"] = "C"
os.environ["LC_TIME"] = "C"
generate_zones = imp.load_source("compiled_to_json",
os.path.join(os.path.dirname(__file__),
"compiled-to-json.py")
).generate_zones
def output_tests(source_prefix, zdump_command, io):
all_zones = list(generate_zones(source_prefix))
io.write("""/* tz.js tests (generated by """ + __file__ + """) */
var pass_count = 0, fail_count = 0;
function assert(cond, description)
{
if (cond) {
++pass_count;
} else {
++fail_count;
print("FAIL: " + description);
}
}
function is(value, expected, description)
{
assert(value == expected,
description + ": " + value + " should equal " + expected);
}
function check_offset(zone, d, utcoff, abbr)
{
var z = tz.zoneAt(zone, new Date(d * 1000));
is(z.offset, utcoff, zone + " at " + d);
is(z.abbr, abbr, zone + " at " + d);
}
/*
* Check a non-round-second values, since the tests below are largely round.
*
* The last two could become invalid in the future.
*/
check_offset("America/Los_Angeles", 1300010399.999, -28800, "PST");
check_offset("America/Los_Angeles", 1300010400.001, -25200, "PDT");
check_offset("America/Los_Angeles", 1308469553.734, -25200, "PDT");
check_offset("America/Los_Angeles", 2519888399.999, -25200, "PDT");
check_offset("America/Los_Angeles", 2519888400.001, -28800, "PST");
/*
* Use the same values to test Etc/UTC, which we don't otherwise test.
*/
check_offset("Etc/UTC", 1300010399.999, 0, "UTC");
check_offset("Etc/UTC", 1300010400, 0, "UTC");
check_offset("Etc/UTC", 1300010400.001, 0, "UTC");
check_offset("Etc/UTC", 1308469553.734, 0, "UTC");
check_offset("Etc/UTC", 2519888399.999, 0, "UTC");
check_offset("Etc/UTC", 2519888400, 0, "UTC");
check_offset("Etc/UTC", 2519888400.001, 0, "UTC");
""")
def output_check_offset(zone, d, utcoff, abbr):
io.write("check_offset(\"{0}\", {1}, {2}, \"{3}\");\n" \
.format(zone, d, utcoff, abbr));
date_zone_re = re.compile("^([^ ]*) ([+-])(\d{2}):(\d{2}):(\d{2})$")
def write_expected(time):
return "@" + str(math.trunc(time))
def read_expected(dateprocess):
(abbr, sign, hours, mins, secs) = date_zone_re.match(
dateprocess.stdout.readline().rstrip("\n")).groups()
utcoff = ((sign == "+") * 2 - 1) * \
(3600 * int(hours) + 60 * int(mins) + int(secs))
return (utcoff, abbr)
def expected_for(zone, time):
date_process = subprocess.Popen(['date',
'--date=' + write_expected(time),
'+%Z %::z'],
stdout = subprocess.PIPE,
env={"TZ": os.path.join(source_prefix, zone)})
result = read_expected(date_process)
date_process.stdout.close()
return result
io.write("""
/*
* Generate tests based on all the transitions shown by zdump for each zone.
*/
""")
sys.stderr.write("Preparing to build transition tests.\n")
date_process = subprocess.Popen(['date',
'--date=' + str(STOP_YEAR) +
'-01-01 00:00:00 UTC', '+%s'],
stdout = subprocess.PIPE)
stop_d = int(date_process.stdout.read().rstrip("\n"))
date_process.stdout.close()
def zdump_for(zone):
zdump = subprocess.Popen([zdump_command,
'-v',
'-c', str(START_YEAR) + "," + str(STOP_YEAR),
zone],
stdout=subprocess.PIPE)
zdump_re = re.compile("^" + zone + " ([^=]+) = ([^=]+) isdst=([01]) gmtoff=(-?\d+)$")
for line in zdump.stdout:
line = line.rstrip("\n")
# Only needed for older zdump:
if line.endswith(" = NULL"):
continue
# Only needed for newer zdump:
if ("(localtime failed)" in line) or ("(gmtime failed)" in line):
continue
groups = zdump_re.match(line).groups()
# Only needed for newer zdump:
if groups[0].endswith("-2147481748 UT") or groups[0].endswith("2147485547 UT"):
continue
yield groups
# FIXME: spread this across cores
zdumps = [(zone, list(zdump_for(zone))) for zone in all_zones]
# Write all the dates to one file and run them through a single
# date process, for speed.
datefile = tempfile.NamedTemporaryFile(delete=False)
for (zone, zdump) in zdumps:
for (date_utc, date_loc, isdst, utcoff) in zdump:
datefile.write(date_utc + "\n")
datefile.close()
date_process = subprocess.Popen(['date',
'--file=' + datefile.name, '+%s'],
stdout = subprocess.PIPE,
env={"TZ": "UTC"})
prev_zone = None
for (zone, zdump) in zdumps:
if zone != prev_zone:
prev_zone = zone
sys.stderr.write("Building transition tests for zone " + zone + "\n")
def output_test(d, utcoff, abbr):
output_check_offset(zone, d, utcoff, abbr)
first = True
first_after_1970 = True
prev_utcoff = None
prev_abbr = None
for (date_utc, date_loc, isdst, utcoff) in zdump:
isdst = bool(isdst) # not really needed
utcoff = int(utcoff)
d = int(date_process.stdout.readline().rstrip("\n"))
abbr = date_loc.split(" ")[-1]
if d >= 0:
if first_after_1970 and d != 0 and not first:
output_test(0, prev_utcoff, prev_abbr)
if first and d > 0:
output_test(0, utcoff, abbr)
output_test(d, utcoff, abbr)
first_after_1970 = False
first = False
prev_utcoff = utcoff
prev_abbr = abbr
if first:
# This zone (Pacific/Johnston) has no transitions, but we
# can still test it.
(prev_utcoff, prev_abbr) = expected_for(zone, 0)
if first_after_1970:
output_test(0, prev_utcoff, prev_abbr)
output_test(stop_d, prev_utcoff, prev_abbr)
date_process.stdout.close()
os.unlink(datefile.name)
io.write("""
/*
* Generate a fixed set of random tests using a linear-congruential
* PRNG. This does a good bit of testing of the space in a random way,
* but uses a fixed random seed to always get the same set of tests.
* See http://en.wikipedia.org/wiki/Linear_congruential_generator (using
* the numbers from Numerical Recipes).
*
* And while we're here, toss in some tests for midnight boundaries
* around the new year.
*/
""")
def lc_prng(): # a generator
# a randomly (once) generated number in [0,2^32)
rand_state = 1938266273;
while True:
yield 1.0 * rand_state / 0x100000000 # value in [0,1)
rand_state = ((rand_state * 1664525) + 1013904223) % 0x100000000
prng = lc_prng()
def random_time():
# pick a random time in 1970...STOP_SECS. Use two random
# numbers so we use the full space, random down to the
# millisecond.
time = (prng.next() * STOP_SECS) + (prng.next() * 0x100000000 / 1000)
time = time % STOP_SECS
time = math.floor(time * 1000) / 1000
return time
def midnight_rule_time(i):
# return 2049-12-31 00:30 UTC + i hours
return 2524523400 + 3600 * i
# For each time zone, we make 100 random tests, and some additional
# tests. Do each zone together so that we can easily use a single
# date process for each zone.
for zone in all_zones:
sys.stderr.write("Building tests for zone " + zone + "\n")
# 100 random tests, then specifically test 48 hours around new
# years 2050 to test rule edge cases
test_times = [random_time() for i in range(100)] + \
[midnight_rule_time(i) for i in range(48)]
# Write all the dates to one file and run them through a single
# date process, for speed.
datefile = tempfile.NamedTemporaryFile(delete=False)
for time in test_times:
datefile.write(write_expected(time) + "\n")
datefile.close()
# FIXME: This is using the system's date command, which might
# not be compatible with the timezone data it's being given.
# (For example, if you have a system date command that doesn't
# understand version 3 timezone file formats, you'll fail some
# post-2038 tests for America/Godthab.)
date_process = subprocess.Popen(['date',
'--file=' + datefile.name,
'+%Z %::z'],
stdout = subprocess.PIPE,
env={"TZ": os.path.join(source_prefix, zone)})
for time in test_times:
(utcoff, abbr) = read_expected(date_process)
output_check_offset(zone, time, utcoff, abbr)
date_process.stdout.close()
os.unlink(datefile.name)
io.write("""
/*
* Some fixed tests for tz.datesFor
*/
var df = tz.datesFor("America/Los_Angeles", 2011, 1, 1, 0, 0, 0);
is(df.length, 1, "datesFor (1) length");
is(df[0].offset, -28800, "datesFor(1) [0].offset");
is(df[0].abbr, "PST", "datesFor(1) [0].abbr");
is(df[0].date.valueOf(), 1293868800000, "datesFor(1) [0].date.valueOf()");
df = tz.datesFor("America/Los_Angeles", 2011, 3, 13, 2, 30, 0);
is(df.length, 0, "datesFor (2) length");
df = tz.datesFor("America/Los_Angeles", 2011, 11, 6, 1, 30, 0);
is(df.length, 2, "datesFor (3) length");
is(df[0].offset, -25200, "datesFor(3) [0].offset");
is(df[0].abbr, "PDT", "datesFor(3) [0].abbr");
is(df[0].date.valueOf(), 1320568200000, "datesFor(3) [0].date.valueOf()");
is(df[1].offset, -28800, "datesFor(3) [1].offset");
is(df[1].abbr, "PST", "datesFor(3) [1].abbr");
is(df[1].date.valueOf(), 1320571800000, "datesFor(3) [1].date.valueOf()");
""")
io.write("""
print("Totals: " + pass_count + " passed, " + fail_count + " failed.");
if (typeof module !== "undefined" && module.exports) {
module.exports = {pass_count: pass_count, fail_count: fail_count };
}
""")
if __name__ == '__main__':
op = OptionParser()
(options, args) = op.parse_args()
if len(args) == 2:
output_tests(args[0], args[1], sys.stdout)
else:
op.error("expected three arguments (zoneinfo directory, zdump command)")