/
githubmirror
/
meteor
Обзор
Документация
Войти
/
githubmirror
/
meteor
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
devel
packages/constraint-solver/input-tests.js
772 строки
20 KB
denihs
Merge branch 'devel' into release-3.0
19 дек 2023, 23:50
19 дек 2023, 23:50
7cb79a7
Код
Авторство
О чём код?
var CS = ConstraintSolver; // "Input tests" are the new style of tests that operate by creating a // CS.Input (representing a problem statement) and passing it into // CS.PackagesResolver. // For tests of CS.Input serialization, see constraint-solver-tests.js. // Yeah, we rely on object key order here. Specifically that // if you add a bunch of keys to an object (that look like package // names) and then JSON.stringify the object, the keys will appear // in that order. If that's not true, these tests will break. var sortKeys = function (obj) { var result = {}; Object.keys(obj).sort().forEach(function (k) { result[k] = obj[k]; }); return result; }; var formatSolution = function (obj) { // Note that we use JSON so that it's easy to copy-and-paste test // results into tests. return JSON.stringify({ answer: sortKeys(obj.answer), allAnswers: obj.allAnswers && obj.allAnswers.map(sortKeys), neededToUseUnanticipatedPrereleases: obj.neededToUseUnanticipatedPrereleases }, null, 2); }; var doTest = async function (test, inputJSONable, outputJSONable, options) { var input; if (inputJSONable instanceof CS.Input) { input = inputJSONable; } else { input = CS.Input.fromJSONable(inputJSONable); } if (typeof outputJSONable.neededToUseUnanticipatedPrereleases !== 'boolean') { outputJSONable = Object.assign(outputJSONable, { neededToUseUnanticipatedPrereleases: ( !! outputJSONable.neededToUseUnanticipatedPrereleases) }); } test.equal( formatSolution(await CS.PackagesResolver._resolveWithInput(input, options)), formatSolution(outputJSONable)); }; var doFailTest = async function (test, inputJSONable, messageExpect) { var input; if (inputJSONable instanceof CS.Input) { input = inputJSONable; } else { input = CS.Input.fromJSONable(inputJSONable); } await test.throwsAsync(async function () { try { await CS.PackagesResolver._resolveWithInput(input); } catch (e) { if (! e.constraintSolverError) { test.fail(e.message); } throw e; } }, messageExpect); }; Tinytest.addAsync("constraint solver - input - upgrade indirect dependency", async function (test) { await doTest(test, { dependencies: ["foo"], constraints: [], previousSolution: { foo: "1.0.0", bar: "2.0.0" }, upgrade: ["bar"], catalogCache: { data: { "foo 1.0.0": ["bar@2.0.0"], "bar 2.0.0": [], "bar 2.0.1": [] } } }, { // if you specify an indirect dependency in `meteor update`, // it will get bumped up to a newer version answer: { foo: "1.0.0", bar: "2.0.1" } }); }); Tinytest.addAsync("constraint solver - input - upgrade direct, don't break", async function (test) { await doTest(test, { dependencies: ["foo", "bar"], constraints: [], previousSolution: { foo: "1.0.0", bar: "2.0.0" }, upgrade: ["bar"], catalogCache: { data: { "foo 1.0.0": ["bar@2.0.0||3.0.0"], "bar 2.0.0": [], "bar 3.0.0": [] } } }, { answer: { foo: "1.0.0", bar: "2.0.0" } }); // if allowIncompatibleUpdate is set, upgrade bar to 3.0.0 await doTest(test, { dependencies: ["foo", "bar"], constraints: [], previousSolution: { foo: "1.0.0", bar: "2.0.0" }, upgrade: ["bar"], allowIncompatibleUpdate: true, catalogCache: { data: { "foo 1.0.0": ["bar@2.0.0||3.0.0"], "bar 2.0.0": [], "bar 3.0.0": [] } } }, { answer: { foo: "1.0.0", bar: "3.0.0" } }); }); Tinytest.addAsync("constraint solver - input - previous solution no patch", async function (test) { await doTest(test, { dependencies: ["foo"], constraints: [], previousSolution: { foo: "1.0.0", bar: "2.0.0" }, catalogCache: { data: { "foo 1.0.0": ["bar@2.0.0"], "foo 1.0.1": ["bar@2.0.1"], "bar 2.0.0": [], "bar 2.0.1": [] } } }, { answer: { foo: "1.0.0", bar: "2.0.0" } }); }); Tinytest.addAsync("constraint solver - input - don't break root dep", async function (test) { await doTest(test, { dependencies: ["foo", "bar"], constraints: [], previousSolution: { bar: "2.0.0" }, upgrade: [], catalogCache: { data: { "foo 1.0.0": ["bar@=2.0.1"], "bar 2.0.0": [], "bar 2.0.1": [] } } }, { answer: { foo: "1.0.0", bar: "2.0.1" } }); await doFailTest(test, { dependencies: ["foo", "bar"], constraints: [], previousSolution: { bar: "2.0.1" }, upgrade: [], catalogCache: { data: { "foo 1.0.0": ["bar@=2.0.0"], "bar 2.0.0": [], "bar 2.0.1": [] } } }, 'Potentially incompatible change required to top-level dependency: bar 2.0.0, was 2.0.1.\nConstraints on package "bar":\n* bar@=2.0.0 <- foo 1.0.0\n\nTo allow potentially incompatible changes to top-level dependencies, you must pass --allow-incompatible-update on the command line.'); }); Tinytest.addAsync("constraint solver - input - don't pick RCs", async function (test) { // First verify that the solver takes the latest version when // presented with a new root dependency (i.e. one not mentioned in // a previous solution) -- and also that it will take a prerelease if // it has no choice. await doTest(test, { dependencies: ["a"], constraints: [], catalogCache: { data: { "a 1.0.0-pre.0": [], "a 1.0.0-pre.1": [] } } }, { answer: { a: "1.0.0-pre.1" }, neededToUseUnanticipatedPrereleases: true }); // If we have the option to take a non-pre-release version, // we should. await doTest(test, { dependencies: ["a"], constraints: [], catalogCache: { data: { "a 0.9.0": [], "a 1.0.0-pre.0": [], "a 1.0.0-pre.1": [] } } }, { answer: { a: "0.9.0" }, neededToUseUnanticipatedPrereleases: false }); // If the prerelease versions are "anticipated", take // the latest one. await doTest(test, { dependencies: ["a"], constraints: [], anticipatedPrereleases: { a: { "1.0.0-pre.0": true, "1.0.0-pre.1": true } }, catalogCache: { data: { "a 0.9.0": [], "a 1.0.0-pre.0": [], "a 1.0.0-pre.1": [] } } }, { answer: { a: "1.0.0-pre.1" }, neededToUseUnanticipatedPrereleases: false }); // Don't take the unanticipated pre-releases here. await doTest(test, { dependencies: ["a"], constraints: ["a@1.0.0"], catalogCache: { data: { "a 1.0.0": [], "a 1.0.1-pre.0": [], "a 1.0.1-pre.1": [] } } }, { answer: { a: "1.0.0" }, neededToUseUnanticipatedPrereleases: false }); // If we ask for one prerelease, we might get another. // If it isn't anticipated, it sets the flag on the result // (differs from older behavior). await doTest(test, { dependencies: ["a"], constraints: ["a@1.0.1-pre.0"], anticipatedPrereleases: { a: { "1.0.0-pre.0": true } }, catalogCache: { data: { "a 1.0.0": [], "a 1.0.1-pre.0": [], "a 1.0.1-pre.1": [] } } }, { answer: { a: "1.0.1-pre.1" }, neededToUseUnanticipatedPrereleases: true }); }); Tinytest.addAsync("constraint solver - input - previous solution no longer needed", async function (test) { await doTest(test, { dependencies: ["foo"], constraints: [], previousSolution: { foo: "1.0.0", bar: "1.0.0" }, catalogCache: { data: { "foo 0.1.0": ["bar@1.0.0"], "foo 1.0.0": [], "bar 1.0.0": [] } } }, { answer: { foo: "1.0.0" } }); }); Tinytest.addAsync("constraint solver - input - conflicting top-level constraints", async function (test) { // conflicting dependencies don't matter if we don't need the package await doTest(test, { dependencies: [], constraints: ["foo@1.0.0", "foo@2.0.0"], previousSolution: {}, catalogCache: { data: { "foo 1.0.0": [], "foo 2.0.0": [] } } }, { answer: { } }); // ... but they do if we do await doFailTest(test, { dependencies: ["bar"], constraints: ["foo@1.0.0", "foo@2.0.0"], previousSolution: {}, catalogCache: { data: { "foo 1.0.0": [], "foo 2.0.0": [], "bar 1.0.0": ["foo"] } } }, /No version of foo satisfies all constraints: @1.0.0, @2.0.0/); }); Tinytest.addAsync("constraint solver - input - unknown package", async function (test) { await doFailTest(test, { dependencies: ["bar"], constraints: [], catalogCache: { data: {} } }, /unknown package in top-level dependencies: bar/); await doFailTest(test, { dependencies: ['foo'], constraints: [], catalogCache: { data: { "foo 1.0.0": ["bar"] } } }, /unknown package: bar\nRequired by: foo 1.0.0/); await doFailTest(test, { dependencies: ["isobuild:bar"], constraints: [], catalogCache: { data: {} } }, /unsupported Isobuild feature "isobuild:bar" in top-level dependencies/); await doFailTest(test, { dependencies: ['foo'], constraints: [], catalogCache: { data: { "foo 1.0.0": ["isobuild:bar"] } } }, /unsupported Isobuild feature "isobuild:bar";.*\nRequired by: foo 1.0.0/); }); Tinytest.addAsync("constraint solver - input - previous indirect deps", async function (test) { await doTest(test, { dependencies: ["a"], constraints: [], previousSolution: { c: "1.2.3" }, catalogCache: { data: { "a 1.0.0": ["b"], "b 1.0.0": ["c"], "c 1.0.0": [], "c 1.2.2": [], "c 1.2.3": [], "c 1.2.4": [], "c 1.3.0": [], "c 2.0.0": [] } } }, { answer: { a: "1.0.0", b: "1.0.0", c: "1.2.3" // take same version as in previous solution } }); }); Tinytest.addAsync("constraint solver - input - new indirect deps", async function (test) { await doTest(test, { dependencies: ["a"], constraints: [], previousSolution: {}, catalogCache: { data: { "a 1.0.0": ["b"], "b 1.0.0": ["c@1.2.0"], "c 1.0.0": [], "c 1.2.2": [], "c 1.2.3": [], "c 1.2.4": [], "c 1.3.0": [], "c 2.0.0": [] } } }, { answer: { a: "1.0.0", b: "1.0.0", c: "1.2.4" // take patches only (use oldest major/minor possible) } }); }); Tinytest.addAsync("constraint solver - input - trade-off", async function (test) { await doTest(test, { dependencies: ["a"], constraints: [], previousSolution: { b: "1.0.0", c: "1.0.0" }, catalogCache: { data: { "a 1.0.0": ["b", "c"], "b 1.0.0": ["x"], "b 1.0.1": ["y@1.0.0"], "b 1.0.2": ["y@2.0.0"], "c 1.0.0": ["x"], "c 1.0.1": ["y@2.0.0"], "c 1.0.2": ["x"], "c 1.0.3": ["y@1.0.0"], "y 1.0.0": [], "y 2.0.0": [] } } }, { // given a choice between (b,c) being (1.0.1, 1.0.3) or (1.0.2, 1.0.1), // the latter should be preferred; indirect dependencies with a previous // solution should be jointly made as old as possible. answer: { a: "1.0.0", b: "1.0.2", c: "1.0.1", y: "2.0.0" } }); await doTest(test, { dependencies: ["a", "b", "c"], constraints: [], previousSolution: {}, catalogCache: { data: { "a 1.0.0": ["b", "c"], "b 1.0.0": ["x"], "b 1.0.1": ["y@1.0.0"], "b 1.0.2": ["y@2.0.0"], "c 1.0.0": ["x"], "c 1.0.1": ["y@2.0.0"], "c 1.0.2": ["x"], "c 1.0.3": ["y@1.0.0"], "y 1.0.0": [], "y 2.0.0": [] } } }, { // now we should prefer "b" and "c" to jointly be as new as possible, // because they are direct dependencies with no previous solution. answer: { a: "1.0.0", b: "1.0.1", c: "1.0.3", y: "1.0.0" } }); }); Tinytest.addAsync("constraint solver - input - fake PackageConstraint", async function (test) { // The tool gives us PackageConstraint objects constructed with a different // copy of package-version-parser. If we're not careful in CS.Input or // CS.Solver, this case will throw an error. See comments in CS.Input. var fakeConstraint = new (function () {}); fakeConstraint.package = 'foo'; fakeConstraint.constraintString = '2.0.0'; fakeConstraint.toString = function () { return 'foo@2.0.0'; }; fakeConstraint.versionConstraint = new (function () {}); fakeConstraint.versionConstraint.raw = '2.0.0'; fakeConstraint.versionConstraint.alternatives = [ {type: 'compatible-with', versionString: '2.0.0' } ]; fakeConstraint.versionConstraint.toString = function () { return '2.0.0'; }; await doFailTest(test, new CS.Input(["foo", "bar"], [fakeConstraint], CS.CatalogCache.fromJSONable({ data: { "foo 1.0.0": [], "foo 2.0.0": [], "bar 1.0.0": ["foo@1.0.0"] } })), /Constraint foo@1.0.0 is not satisfied by foo 2.0.0/); }); Tinytest.addAsync("constraint solver - input - stack overflow bug", async function (test) { // This case is taken from the "solver-error" branch of the meteor/rectangles // repo. It's an app running from a release (new-version-solver-2) with an // unsatisfiable constraint in .meteor/packages. It caused a stack overflow // before logic-solver got smarter about avoiding recursion in formula // generation, and it also tests the case where an unsatisfiable constraint is // in .meteor/packages. // // It's not actually a good test of logic-solver overflowing the stack anymore, // because the constraint-solver is smarter now. await doFailTest(test, STACK_OVERFLOW_BUG_INPUT, /No version of follower-livedata satisfies all constraints: @0.9.0/); }); Tinytest.add("constraint solver - input - bad package name", function (test) { test.throws(function () { new CS.Input(['-x'], [], new CS.CatalogCache()); }, /may not begin with a hyphen/); test.throws(function () { new CS.Input([], [], new CS.CatalogCache(), { previousSolution: { $a: '1.0.0' } }); }, /Package names can only contain/); test.throws(function () { new CS.Input([], [], new CS.CatalogCache(), { upgrade: ['$a'] }); }, /Package names can only contain/); test.throws(function () { new CS.Input([], [], new CS.CatalogCache(), { upgrade: ['-a'] }); }, /may not begin with a hyphen/); }); Tinytest.addAsync("constraint solver - input - slow solve", async function (test) { var input = CS.Input.fromJSONable(SLOW_TEST_DATA); test.equal( formatSolution(await CS.PackagesResolver._resolveWithInput(input)), formatSolution({ "answer":{ "autopublish":"1.0.2", "u2622:persistent-session":"0.2.1", "blaze":"2.0.4", "random":"1.0.2", "mobile-status-bar":"1.0.2", "deps":"1.0.6", "follower-livedata":"1.0.3", "spacebars":"1.0.4", "spacebars-compiler":"1.0.4", "launch-screen":"1.0.1", "iron:location":"1.0.6", "http":"1.0.9", "json":"1.0.2", "check":"1.0.3", "retry":"1.0.2", "id-map":"1.0.2", "reactive-dict":"1.0.5", "mrt:moment":"2.8.1", "callback-hook":"1.0.2", "meteor":"1.1.4", "fastclick":"1.0.2", "minifiers":"1.1.3", "mrt:jquery-ui-sortable":"1.10.3", "webapp":"1.1.5", "ejson":"1.0.5", "skinnygeek1010:parse-form":"0.2.1", "iron:controller":"1.0.6", "base64":"1.0.2", "url":"1.0.3", "blaze-tools":"1.0.2", "ddp":"1.0.13", "iron:core":"1.0.6", "splendido:accounts-templates-semantic-ui":"0.0.4", "observe-sequence":"1.0.4", "reactive-var":"1.0.4", "webapp-hashing":"1.0.2", "mongo":"1.0.11", "htmljs":"1.0.3", "ui":"1.0.5", "amplify":"1.0.0", "meteor-platform":"1.2.1", "ordered-dict":"1.0.2", "session":"1.0.5", "livedata":"1.0.12", "templating":"1.0.10", "binary-heap":"1.0.2", "mizzao:timesync":"0.2.2", "tracker":"1.0.4", "autoupdate":"1.1.4", "html-tools":"1.0.3", "reload":"1.1.2", "less":"1.0.12", "application-configuration":"1.0.4", "gfk:notifications":"1.1.1", "underscore":"1.0.2", "iron:dynamic-template":"1.0.6", "routepolicy":"1.0.3", "iron:router":"1.0.6", "insecure":"1.0.2", "iron:layout":"1.0.6", "geojson-utils":"1.0.2", "minimongo":"1.0.6", "iron:url":"1.0.6", "jquery":"1.11.2", "boilerplate-generator":"1.0.2", "iron:middleware-stack":"1.0.6", "logging":"1.0.6" }, "neededToUseUnanticipatedPrereleases":false })); }); Tinytest.addAsync("constraint solver - input - update unknown", async function (test) { // trying to update an unknown package is currently not an error // at the CS.Input level. It IS an error in the tool, so this case // won't make it through in actual tool usage. await doTest(test, { dependencies: ["direct"], constraints: [], previousSolution: { direct: "1.0.0" }, upgrade: ["unknown"], catalogCache: { data: { "direct 1.0.0": [] } } }, { answer: { direct: "1.0.0" } }); }); Tinytest.addAsync("constraint solver - input - update indirect deps", async function (test) { // test upgrading an indirect dependency explicitly. // `meteor update indirect` takes it from 1.0.0 to 2.0.0 (with no concern // about bumping the major version because it's not a top-level package, // and the only package that uses it doesn't specify any constraint). await doTest(test, { dependencies: ["direct"], constraints: [], previousSolution: { direct: "1.0.0", indirect: "1.0.0" }, upgrade: ["indirect"], catalogCache: { data: { "direct 1.0.0": ["indirect"], "indirect 1.0.0": [], "indirect 2.0.0": [] } } }, { answer: { direct: "1.0.0", indirect: "2.0.0" } }); // Normally, we don't take patches to indirect dependencies, even when // updating direct dependencies. This is what would happen if the user // typed `meteor update direct`. await doTest(test, { dependencies: ["direct"], constraints: [], previousSolution: { direct: "1.0.0", indirect: "1.0.0" }, upgrade: ["direct"], catalogCache: { data: { "direct 1.0.0": ["indirect"], "direct 1.5.0": ["indirect"], "direct 2.0.0": ["indirect"], "indirect 1.0.0": [], "indirect 1.0.1": [], "indirect 1.1.0": [] } } }, { answer: { direct: "1.5.0", // upgraded (but not to higher major version) indirect: "1.0.0" // not upgraded } }); // If upgradeIndirectDepPatchVersions is true, user just typed // `meteor update`. Take the opportunity to take patches to indirect // dependencies. await doTest(test, { dependencies: ["direct"], constraints: [], previousSolution: { direct: "1.0.0", indirect: "1.0.0", indirect2: "1.0.0" }, upgrade: ["direct"], upgradeIndirectDepPatchVersions: true, catalogCache: { data: { "direct 1.0.0": ["indirect"], "direct 1.5.0": ["indirect", "indirect2"], "direct 2.0.0": ["indirect"], "indirect 1.0.0": [], "indirect 1.0.0_1": [], "indirect 1.0.1": [], "indirect 1.1.0": [], "indirect2 1.0.0": [], "indirect2 1.0.0_1": [], "indirect2 1.0.0_2": [], "indirect2 1.1.0": [] } } }, { answer: { direct: "1.5.0", // upgraded (but not to higher major version) indirect: "1.0.1", // patch/wrapNum upgraded to latest indirect2: "1.0.0_2" // patch/wrapNum upgraded to latest } }); }); Tinytest.addAsync("constraint solver - input - package is only weak dep", async function (test) { await doTest(test, { dependencies: ["foo"], constraints: [], previousSolution: {}, catalogCache: { data: { "foo 1.0.0": ["?bar"] } } }, { answer: { foo: "1.0.0" } }); });