Skip to content

Commit

Permalink
Merge pull request #1059 from moment/fix-test-generation
Browse files Browse the repository at this point in the history
Fix test generation for data updates
  • Loading branch information
gilmoreorless authored May 7, 2023
2 parents 879029f + 7fa6992 commit fe79bc6
Show file tree
Hide file tree
Showing 601 changed files with 2,031 additions and 621 deletions.
28 changes: 21 additions & 7 deletions tasks/data-collect.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ module.exports = function (grunt) {
meta = grunt.file.readJSON('data/meta/' + version + '.json'),
data = [];

var format = "MMM D HH:mm:ss YYYY";
var format = "MMM D HH:mm:ss YYYY",
invalidLine = /failed|-2147481748|2147485547/;

files.forEach(function (file) {
var lines = grunt.file.read(path.join('temp/zdump/' + version, file)).split('\n'),
Expand All @@ -23,20 +24,33 @@ module.exports = function (grunt) {
countries = [];

lines.forEach(function (line) {
var parts = line.split(/\s+/),
utc = moment.utc(parts.slice(2, 6).join(' '), format),
local = moment.utc(parts.slice(9, 13).join(' '), format);
// Skip any lines that don't match the expected format.
// 64-bit `zdump` with the `-v` flag can produce lines outside Moment's parseable date range:
//
// Etc/GMT-2 -9223372036854775808 = NULL
// Etc/GMT-2 -67768040609748001 (gmtime failed) = -67768040609748001 (localtime failed)
// Etc/GMT-2 -67768040609748000 (gmtime failed) = Thu Jan 1 00:00:00 -2147481748 +02 isdst=0 gmtoff=7200
// Etc/GMT-2 Thu Jan 1 00:00:00 -2147481748 UT = Thu Jan 1 02:00:00 -2147481748 +02 isdst=0 gmtoff=7200
var parts = line.split(/\s+/);
if (invalidLine.test(line) || parts.length < 13) {
return;
}

if (line.search('failed') !== -1) { return; }
if (parts.length < 13) { return; }
var utc = moment.utc(parts.slice(2, 6).join(' '), format),
local = moment.utc(parts.slice(9, 13).join(' '), format);

offsets.push(+utc.diff(local, 'minutes', true).toFixed(4));
untils.push(+utc);
abbrs.push(parts[13]);
});

if (offsets.length === 0 && lines.length === 3 && lines[2].length === 0) {
// use alternate zdump format
// Use alternate `zdump` format when a fixed-offset zone has no transitions.
// The data-zdump task will instead generate the current timestamp for UTC and the zone,
// with the difference between them being used as the offset:
//
// UTC Sun May 7 06:17:50 2023 UTC
// Etc/GMT-2 Sun May 7 08:17:50 2023 +02
var utcParts = lines[0].split(/\s+/),
localParts = lines[1].split(/\s+/);

Expand Down
106 changes: 98 additions & 8 deletions tasks/data-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,18 @@ function yearTest (year, changeTests, name) {
function yearTests (zone) {
var changeTests = {};

zone.untils.forEach(function (until, i) {
if (i < 2 || i >= zone.untils.length - 2) { return; }
var year = moment.utc(until).year();
changeTests[year] = changeTests[year] || [];
changeTests[year].push(changeTest(zone, i));
});
// Fixed-offset zones (like `Etc/GMT-2`) only have a single `null` until value.
// We pick a fixed timestamp for those cases, using the Unix epoch.
if (zone.untils.length === 1 && zone.untils[0] === null) {
var fixedUntil = Object.assign({}, zone, { untils: [0] })
changeTests[1970] = [changeTest(fixedUntil, 0)];
} else {
zone.untils.forEach(function (until, i) {
var year = moment.utc(until).year();
changeTests[year] = changeTests[year] || [];
changeTests[year].push(changeTest(zone, i));
});
}

return Object.keys(changeTests).map(function (year) {
return yearTest(year, changeTests[year], zone.name);
Expand Down Expand Up @@ -73,21 +79,75 @@ function guessTests (zone) {
return tests;
}

/**
* Creates a test for a zone's `.countries()` output
*/
function zoneCountriesTest (zone) {
var countries = zone.countries.slice().sort();
return "\t\ttest.deepEqual(tz.zone('" + zone.name + "').countries(), [" +
countries.map(function (code) { return '"' + code + '"' }).join(',') +
"]);\n";
}

/**
* Creates a test for a country -> zones mapping
*/
function countryZonesTest (country) {
var zones = country.zones.slice().sort();
return '\t\ttest.deepEqual(tz.zonesForCountry("' + country.abbr + '"), [' +
zones.map(function (zone) { return '"' + zone + '"' }).join(',') +
']);\n';
}

/**
* Creates all tests for countries.js
*/
function allCountriesTests (zoneTests, countryTests) {
var intro = '"use strict";\n\nvar moment = require("../../");\nvar tz = moment.tz;\n\nexports.countries = {',
outro = '\n};\n',
tests = [
{ name: 'zone_countries', tests: zoneTests },
{ name: 'country_zones', tests: countryTests },
],
testStrings = tests.map(function (data) {
return '\n\n\t' + data.name + ' : function (test) {\n\n' +
data.tests.sort().join('') +
'\n\t\ttest.done();\n\t}';
});

return intro + testStrings.join(',') + outro;
}

module.exports = function (grunt) {
grunt.registerTask('data-tests', '8. Create unit tests from data-collect.', function (version) {
version = version || 'latest';
tz.load(grunt.file.readJSON('data/packed/' + version + '.json'));
var zones = grunt.file.readJSON('temp/collect/' + version + '.json'),
testBase = version === 'latest' ? 'tests' : path.join('temp/tests', version)
meta = grunt.file.readJSON('data/meta/' + version + '.json'),
testBase = version === 'latest' ? 'tests' : path.join('temp/tests', version),
zoneCountriesTests = [];

// Generate unit tests for each zone
zones.forEach(function (zone) {
var data = intro(zone.name) + guessTests(zone) + yearTests(zone) + '\n};',
dest = path.join(testBase, 'zones', zone.name.toLowerCase() + '.js');

grunt.file.write(dest, data);
zoneCountriesTests.push(zoneCountriesTest(zone));
grunt.verbose.ok("Created " + zone.name + " tests.");
});

// Generate unit tests for countries
if (meta.countries) {
var countryZonesTests = Object.values(meta.countries).map(countryZonesTest);
grunt.file.write(
path.join(testBase, 'countries', 'countries.js'),
allCountriesTests(zoneCountriesTests, countryZonesTests)
);
grunt.verbose.ok("Created country tests.");
}

// Copy helpers and rewrite paths when generating for a specific version
if (version !== 'latest') {
grunt.file.copy('tests/helpers/helpers.js',
path.join(testBase, 'helpers/helpers.js'),
Expand All @@ -105,7 +165,7 @@ module.exports = function (grunt) {
});
};

// Tests should look something like this.
// Zone tests should look something like this.
//
// "use strict";
//
Expand All @@ -130,3 +190,33 @@ module.exports = function (grunt) {
// ["1919-10-26T09:00:00+00:00", "01:00:00", "PST", 480]
// ])
// };

// Country tests should look something like this.
//
// "use strict";
//
// var moment = require("../../");
// var tz = moment.tz;
//
// exports.countries = {
//
// zone_countries : function (test) {
//
// test.deepEqual(tz.zone('Africa/Abidjan').countries(), ["BF","CI","GH","GM","GN","IS","ML","MR","SH","SL","SN","TG"]);
// test.deepEqual(tz.zone('Africa/Accra').countries(), ["GH"]);
// ...
//
// test.done();
// },
//
// country_zones : function (test) {
//
// test.deepEqual(tz.zonesForCountry("AD"), ["Europe/Andorra"]);
// test.deepEqual(tz.zonesForCountry("AE"), ["Asia/Dubai"]);
// test.deepEqual(tz.zonesForCountry("AF"), ["Asia/Kabul"]);
// test.deepEqual(tz.zonesForCountry("AG"), ["America/Antigua","America/Puerto_Rico"]);
// ...
//
// test.done();
// }
// };
31 changes: 27 additions & 4 deletions tasks/data-zdump.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,31 @@ module.exports = function (grunt) {
return result;
}

// Allow using custom zdump binaries
var zdumpBinary = 'zdump',
zdumpPathOption = grunt.option('zdump-path');
if (zdumpPathOption && grunt.file.exists(zdumpPathOption)) {
zdumpBinary = zdumpPathOption;
}

var zdumpVerboseArg = '-V';
// Do a test of `zdump` to make sure it can provide the format we want.
// The `-V` flag (different from `-v`) was introduced in tzcode version 2013d,
// but still isn't available on macOS.
execFile(zdumpBinary, ['-V', 'UTC'], { maxBuffer: 20*1024*1024 }, function (err, stdout, stderr) {
if (stdout === '' && stderr.includes('illegal option')) {
zdumpVerboseArg = '-v';
grunt.log.warn('WARNING: The version of `zdump` on this machine is very old and might produce incorrect values');

// Do a separate test to make sure 64-bit data is returned.
// 32-bit `zdump` returns 1901 & 2038 boundaries even for the UTC zone.
} else if (stdout.includes('1901') && stdout.includes('2038')) {
grunt.log.warn("WARNING: The version of `zdump` on this machine can't handle 64-bit data and will produce incorrect values");
}

next();
});

function next () {
if (!files.length) {
grunt.log.ok('Dumped data for ' + version);
Expand All @@ -32,13 +57,13 @@ module.exports = function (grunt) {
src = path.join(zicBase, file),
dest = path.join(zdumpBase, file);

execFile('zdump', ['-v', src], { maxBuffer: 20*1024*1024 }, function (err, stdout) {
execFile(zdumpBinary, [zdumpVerboseArg, src], { maxBuffer: 20*1024*1024 }, function (err, stdout) {
if (err) { throw err; }

if (stdout.length === 0) {
// on some systems, when there are no transitions then we have
// to get creative to learn the offset and abbreviation
execFile('zdump', ['UTC', src], { maxBuffer: 20*1024*1024 }, function (_err, _stdout) {
execFile(zdumpBinary, ['UTC', src], { maxBuffer: 20*1024*1024 }, function (_err, _stdout) {
if (_err) { throw _err; }

grunt.file.write(dest + '.zdump', normalizePaths(_stdout));
Expand All @@ -55,7 +80,5 @@ module.exports = function (grunt) {
next();
});
}

next();
});
};
22 changes: 19 additions & 3 deletions tasks/data-zic.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@ module.exports = function (grunt) {

grunt.file.mkdir(dest);

// Allow using custom zic binaries
var zicBinary = 'zic',
zicPathOption = grunt.option('zic-path');
if (zicPathOption && grunt.file.exists(zicPathOption)) {
zicBinary = zicPathOption;
}

// Do a test of `zic` to make sure it can provide the data we want.
// The presence of the `-b` argument (introduced in tzcode 2019b) is a good test, as its
// purpose is to help work around year-2038 bugs.
execFile(zicBinary, ['--help'], function (err, stdout) {
if (!stdout.includes('[ -b ')) {
grunt.log.warn('WARNING: The version of `zic` on this machine is old and might produce incorrect values');
}

next();
});

function next () {
if (!files.length) {
grunt.log.ok('Compiled zic for ' + version);
Expand All @@ -25,15 +43,13 @@ module.exports = function (grunt) {
if (!grunt.file.exists(src)) {
throw new Error("Can't process " + src + " with zic. File doesn't exist");
}
execFile('zic', ['-d', dest, src], function (err) {
execFile(zicBinary, ['-d', dest, src], function (err) {
if (err) { throw err; }

grunt.verbose.ok('Compiled zic ' + version + ':' + file);

next();
});
}

next();
});
};
8 changes: 5 additions & 3 deletions tests/countries/countries.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ exports.countries = {

zone_countries : function (test) {

test.deepEqual(tz.zone("Africa/Abidjan").countries(), ["BF","CI","GH","GM","GN","IS","ML","MR","SH","SL","SN","TG"]);
test.deepEqual(tz.zone('Africa/Abidjan').countries(), ["BF","CI","GH","GM","GN","IS","ML","MR","SH","SL","SN","TG"]);
test.deepEqual(tz.zone('Africa/Accra').countries(), ["GH"]);
test.deepEqual(tz.zone('Africa/Addis_Ababa').countries(), ["ET"]);
test.deepEqual(tz.zone('Africa/Algiers').countries(), ["DZ"]);
Expand Down Expand Up @@ -183,6 +183,7 @@ exports.countries = {
test.deepEqual(tz.zone('America/North_Dakota/Beulah').countries(), ["US"]);
test.deepEqual(tz.zone('America/North_Dakota/Center').countries(), ["US"]);
test.deepEqual(tz.zone('America/North_Dakota/New_Salem').countries(), ["US"]);
test.deepEqual(tz.zone('America/Nuuk').countries(), ["GL"]);
test.deepEqual(tz.zone('America/Ojinaga').countries(), ["MX"]);
test.deepEqual(tz.zone('America/Panama').countries(), ["CA","KY","PA"]);
test.deepEqual(tz.zone('America/Pangnirtung').countries(), []);
Expand All @@ -192,7 +193,7 @@ exports.countries = {
test.deepEqual(tz.zone('America/Port_of_Spain').countries(), ["TT"]);
test.deepEqual(tz.zone('America/Porto_Acre').countries(), []);
test.deepEqual(tz.zone('America/Porto_Velho').countries(), ["BR"]);
test.deepEqual(tz.zone("America/Puerto_Rico").countries(), ["AG","AI","AW","BL","BQ","CA","CW","DM","GD","GP","KN","LC","MF","MS","PR","SX","TT","VC","VG","VI"]);
test.deepEqual(tz.zone('America/Puerto_Rico').countries(), ["AG","AI","AW","BL","BQ","CA","CW","DM","GD","GP","KN","LC","MF","MS","PR","SX","TT","VC","VG","VI"]);
test.deepEqual(tz.zone('America/Punta_Arenas').countries(), ["CL"]);
test.deepEqual(tz.zone('America/Rainy_River').countries(), []);
test.deepEqual(tz.zone('America/Rankin_Inlet').countries(), ["CA"]);
Expand Down Expand Up @@ -455,8 +456,8 @@ exports.countries = {
test.deepEqual(tz.zone('Europe/Jersey').countries(), ["JE"]);
test.deepEqual(tz.zone('Europe/Kaliningrad').countries(), ["RU"]);
test.deepEqual(tz.zone('Europe/Kiev').countries(), []);
test.deepEqual(tz.zone('Europe/Kyiv').countries(), ["UA"]);
test.deepEqual(tz.zone('Europe/Kirov').countries(), ["RU"]);
test.deepEqual(tz.zone('Europe/Kyiv').countries(), ["UA"]);
test.deepEqual(tz.zone('Europe/Lisbon').countries(), ["PT"]);
test.deepEqual(tz.zone('Europe/Ljubljana').countries(), ["SI"]);
test.deepEqual(tz.zone('Europe/London').countries(), ["GB","GG","IM","JE"]);
Expand Down Expand Up @@ -551,6 +552,7 @@ exports.countries = {
test.deepEqual(tz.zone('Pacific/Guam').countries(), ["GU","MP"]);
test.deepEqual(tz.zone('Pacific/Honolulu').countries(), ["US"]);
test.deepEqual(tz.zone('Pacific/Johnston').countries(), []);
test.deepEqual(tz.zone('Pacific/Kanton').countries(), ["KI"]);
test.deepEqual(tz.zone('Pacific/Kiritimati').countries(), ["KI"]);
test.deepEqual(tz.zone('Pacific/Kosrae').countries(), ["FM"]);
test.deepEqual(tz.zone('Pacific/Kwajalein').countries(), ["MH"]);
Expand Down
5 changes: 4 additions & 1 deletion tests/zones/africa/abidjan.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@ exports["Africa/Abidjan"] = {

"guess:by:abbr" : helpers.makeTestGuess("Africa/Abidjan", { abbr: true }),


"1912" : helpers.makeTestYear("Africa/Abidjan", [
["1912-01-01T00:16:07+00:00", "23:59:59", "LMT", 968 / 60],
["1912-01-01T00:16:08+00:00", "00:16:08", "GMT", 0]
])
};
5 changes: 4 additions & 1 deletion tests/zones/africa/accra.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@ exports["Africa/Accra"] = {

"guess:by:abbr" : helpers.makeTestGuess("Africa/Accra", { abbr: true, expect: "Africa/Abidjan" }),


"1912" : helpers.makeTestYear("Africa/Accra", [
["1912-01-01T00:16:07+00:00", "23:59:59", "LMT", 968 / 60],
["1912-01-01T00:16:08+00:00", "00:16:08", "GMT", 0]
])
};
4 changes: 3 additions & 1 deletion tests/zones/africa/addis_ababa.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ exports["Africa/Addis_Ababa"] = {
"guess:by:abbr" : helpers.makeTestGuess("Africa/Addis_Ababa", { abbr: true, expect: "Africa/Nairobi" }),

"1908" : helpers.makeTestYear("Africa/Addis_Ababa", [
["1908-04-30T21:32:43+00:00", "23:59:59", "LMT", -8836 / 60],
["1908-04-30T21:32:44+00:00", "00:02:44", "+0230", -150]
]),

Expand All @@ -28,6 +29,7 @@ exports["Africa/Addis_Ababa"] = {
]),

"1942" : helpers.makeTestYear("Africa/Addis_Ababa", [
["1942-07-31T21:14:59+00:00", "23:59:59", "+0245", -165]
["1942-07-31T21:14:59+00:00", "23:59:59", "+0245", -165],
["1942-07-31T21:15:00+00:00", "00:15:00", "EAT", -180]
])
};
4 changes: 3 additions & 1 deletion tests/zones/africa/algiers.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ exports["Africa/Algiers"] = {
"guess:by:abbr" : helpers.makeTestGuess("Africa/Algiers", { abbr: true }),

"1891" : helpers.makeTestYear("Africa/Algiers", [
["1891-03-15T23:47:47+00:00", "23:59:59", "LMT", -732 / 60],
["1891-03-15T23:47:48+00:00", "23:57:09", "PMT", -561 / 60]
]),

Expand Down Expand Up @@ -134,6 +135,7 @@ exports["Africa/Algiers"] = {
]),

"1981" : helpers.makeTestYear("Africa/Algiers", [
["1981-04-30T23:59:59+00:00", "23:59:59", "WET", 0]
["1981-04-30T23:59:59+00:00", "23:59:59", "WET", 0],
["1981-05-01T00:00:00+00:00", "01:00:00", "CET", -60]
])
};
Loading

0 comments on commit fe79bc6

Please sign in to comment.