зеркало из https://github.com/mozilla/gecko-dev.git
Bug 557849: Make Addon.updateAutomatically persist and work. r=robstrong
* * * Bug 559876: test_update.js is failing intermittently.
This commit is contained in:
Родитель
735d0220d5
Коммит
4d8ef9957e
|
@ -277,15 +277,20 @@ var AddonManagerInternal = {
|
||||||
Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", scope);
|
Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", scope);
|
||||||
scope.LightweightThemeManager.updateCurrentTheme();
|
scope.LightweightThemeManager.updateCurrentTheme();
|
||||||
|
|
||||||
this.getAddonsByTypes(null, function getAddonsCallback(aAddons) {
|
this.getAllAddons(function getAddonsCallback(aAddons) {
|
||||||
aAddons.forEach(function BUC_forEachCallback(aAddon) {
|
aAddons.forEach(function BUC_forEachCallback(aAddon) {
|
||||||
if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE) {
|
// Check all add-ons for updates so that any compatibility updates will
|
||||||
aAddon.findUpdates({
|
// be applied
|
||||||
onUpdateAvailable: function BUC_onUpdateAvailable(aAddon, aInstall) {
|
aAddon.findUpdates({
|
||||||
|
onUpdateAvailable: function BUC_onUpdateAvailable(aAddon, aInstall) {
|
||||||
|
// Start installing updates when the add-on can be updated and
|
||||||
|
// background updates should be applied.
|
||||||
|
if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE &&
|
||||||
|
aAddon.applyBackgroundUpdates) {
|
||||||
aInstall.install();
|
aInstall.install();
|
||||||
}
|
}
|
||||||
}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
|
}
|
||||||
}
|
}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -402,6 +402,8 @@ function loadManifestFromRDF(aUri, aStream) {
|
||||||
addon.userDisabled = false;
|
addon.userDisabled = false;
|
||||||
addon.appDisabled = !isUsableAddon(addon);
|
addon.appDisabled = !isUsableAddon(addon);
|
||||||
|
|
||||||
|
addon.applyBackgroundUpdates = true;
|
||||||
|
|
||||||
return addon;
|
return addon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1177,8 +1179,6 @@ var XPIProvider = {
|
||||||
|
|
||||||
// Set the additional properties on the new AddonInternal
|
// Set the additional properties on the new AddonInternal
|
||||||
newAddon._installLocation = aInstallLocation;
|
newAddon._installLocation = aInstallLocation;
|
||||||
newAddon.userDisabled = aOldAddon.userDisabled;
|
|
||||||
newAddon.installDate = aOldAddon.installDate;
|
|
||||||
newAddon.updateDate = aAddonState.mtime;
|
newAddon.updateDate = aAddonState.mtime;
|
||||||
newAddon.visible = !(newAddon.id in visibleAddons);
|
newAddon.visible = !(newAddon.id in visibleAddons);
|
||||||
|
|
||||||
|
@ -2182,7 +2182,8 @@ var XPIProvider = {
|
||||||
const FIELDS_ADDON = "internal_id, id, location, version, type, internalName, " +
|
const FIELDS_ADDON = "internal_id, id, location, version, type, internalName, " +
|
||||||
"updateURL, updateKey, optionsURL, aboutURL, iconURL, " +
|
"updateURL, updateKey, optionsURL, aboutURL, iconURL, " +
|
||||||
"defaultLocale, visible, active, userDisabled, appDisabled, " +
|
"defaultLocale, visible, active, userDisabled, appDisabled, " +
|
||||||
"pendingUninstall, descriptor, installDate, updateDate";
|
"pendingUninstall, descriptor, installDate, updateDate, " +
|
||||||
|
"applyBackgroundUpdates";
|
||||||
|
|
||||||
// A helper function to simply log any errors that occur during async statements.
|
// A helper function to simply log any errors that occur during async statements.
|
||||||
function asyncErrorLogger(aError) {
|
function asyncErrorLogger(aError) {
|
||||||
|
@ -2218,7 +2219,7 @@ var XPIDatabase = {
|
||||||
":updateKey, :optionsURL, :aboutURL, :iconURL, " +
|
":updateKey, :optionsURL, :aboutURL, :iconURL, " +
|
||||||
":locale, :visible, :active, :userDisabled," +
|
":locale, :visible, :active, :userDisabled," +
|
||||||
" :appDisabled, 0, :descriptor, :installDate, " +
|
" :appDisabled, 0, :descriptor, :installDate, " +
|
||||||
":updateDate)",
|
":updateDate, :applyBackgroundUpdates)",
|
||||||
addAddonMetadata_addon_locale: "INSERT INTO addon_locale VALUES " +
|
addAddonMetadata_addon_locale: "INSERT INTO addon_locale VALUES " +
|
||||||
"(:internal_id, :name, :locale)",
|
"(:internal_id, :name, :locale)",
|
||||||
addAddonMetadata_locale: "INSERT INTO locale (name, description, creator, " +
|
addAddonMetadata_locale: "INSERT INTO locale (name, description, creator, " +
|
||||||
|
@ -2261,7 +2262,8 @@ var XPIDatabase = {
|
||||||
"1 - appDisabled, 1 - pendingUninstall)",
|
"1 - appDisabled, 1 - pendingUninstall)",
|
||||||
setAddonProperties: "UPDATE addon SET userDisabled=:userDisabled, " +
|
setAddonProperties: "UPDATE addon SET userDisabled=:userDisabled, " +
|
||||||
"appDisabled=:appDisabled, " +
|
"appDisabled=:appDisabled, " +
|
||||||
"pendingUninstall=:pendingUninstall WHERE " +
|
"pendingUninstall=:pendingUninstall, " +
|
||||||
|
"applyBackgroundUpdates=:applyBackgroundUpdates WHERE " +
|
||||||
"internal_id=:internal_id",
|
"internal_id=:internal_id",
|
||||||
updateTargetApplications: "UPDATE targetApplication SET " +
|
updateTargetApplications: "UPDATE targetApplication SET " +
|
||||||
"minVersion=:minVersion, maxVersion=:maxVersion " +
|
"minVersion=:minVersion, maxVersion=:maxVersion " +
|
||||||
|
@ -2430,6 +2432,7 @@ var XPIDatabase = {
|
||||||
"userDisabled INTEGER, appDisabled INTEGER, " +
|
"userDisabled INTEGER, appDisabled INTEGER, " +
|
||||||
"pendingUninstall INTEGER, descriptor TEXT, " +
|
"pendingUninstall INTEGER, descriptor TEXT, " +
|
||||||
"installDate INTEGER, updateDate INTEGER, " +
|
"installDate INTEGER, updateDate INTEGER, " +
|
||||||
|
"applyBackgroundUpdates INTEGER, " +
|
||||||
"UNIQUE (id, location)");
|
"UNIQUE (id, location)");
|
||||||
this.connection.createTable("targetApplication",
|
this.connection.createTable("targetApplication",
|
||||||
"addon_internal_id INTEGER, " +
|
"addon_internal_id INTEGER, " +
|
||||||
|
@ -2566,7 +2569,7 @@ var XPIDatabase = {
|
||||||
addon.installDate = aRow.installDate;
|
addon.installDate = aRow.installDate;
|
||||||
addon.updateDate = aRow.updateDate;
|
addon.updateDate = aRow.updateDate;
|
||||||
["visible", "active", "userDisabled", "appDisabled",
|
["visible", "active", "userDisabled", "appDisabled",
|
||||||
"pendingUninstall"].forEach(function(aProp) {
|
"pendingUninstall", "applyBackgroundUpdates"].forEach(function(aProp) {
|
||||||
addon[aProp] = aRow[aProp] != 0;
|
addon[aProp] = aRow[aProp] != 0;
|
||||||
});
|
});
|
||||||
this.addonCache[aRow.internal_id] = Components.utils.getWeakReference(addon);
|
this.addonCache[aRow.internal_id] = Components.utils.getWeakReference(addon);
|
||||||
|
@ -2716,7 +2719,7 @@ var XPIDatabase = {
|
||||||
addon.installDate = aRow.getResultByName("installDate");
|
addon.installDate = aRow.getResultByName("installDate");
|
||||||
addon.updateDate = aRow.getResultByName("updateDate");
|
addon.updateDate = aRow.getResultByName("updateDate");
|
||||||
["visible", "active", "userDisabled", "appDisabled",
|
["visible", "active", "userDisabled", "appDisabled",
|
||||||
"pendingUninstall"].forEach(function(aProp) {
|
"pendingUninstall", "applyBackgroundUpdates"].forEach(function(aProp) {
|
||||||
addon[aProp] = aRow.getResultByName(aProp) != 0;
|
addon[aProp] = aRow.getResultByName(aProp) != 0;
|
||||||
});
|
});
|
||||||
this.addonCache[internal_id] = Components.utils.getWeakReference(addon);
|
this.addonCache[internal_id] = Components.utils.getWeakReference(addon);
|
||||||
|
@ -2990,7 +2993,8 @@ var XPIDatabase = {
|
||||||
stmt.params.installDate = aAddon.installDate;
|
stmt.params.installDate = aAddon.installDate;
|
||||||
stmt.params.updateDate = aAddon.updateDate;
|
stmt.params.updateDate = aAddon.updateDate;
|
||||||
copyProperties(aAddon, PROP_METADATA, stmt.params);
|
copyProperties(aAddon, PROP_METADATA, stmt.params);
|
||||||
["visible", "userDisabled", "appDisabled"].forEach(function(aProp) {
|
["visible", "userDisabled", "appDisabled",
|
||||||
|
"applyBackgroundUpdates"].forEach(function(aProp) {
|
||||||
stmt.params[aProp] = aAddon[aProp] ? 1 : 0;
|
stmt.params[aProp] = aAddon[aProp] ? 1 : 0;
|
||||||
});
|
});
|
||||||
stmt.params.active = (aAddon.visible && !aAddon.userDisabled &&
|
stmt.params.active = (aAddon.visible && !aAddon.userDisabled &&
|
||||||
|
@ -3034,6 +3038,9 @@ var XPIDatabase = {
|
||||||
updateAddonMetadata: function XPIDB_updateAddonMetadata(aOldAddon, aNewAddon,
|
updateAddonMetadata: function XPIDB_updateAddonMetadata(aOldAddon, aNewAddon,
|
||||||
aDescriptor) {
|
aDescriptor) {
|
||||||
this.removeAddonMetadata(aOldAddon);
|
this.removeAddonMetadata(aOldAddon);
|
||||||
|
aNewAddon.userDisabled = aOldAddon.userDisabled;
|
||||||
|
aNewAddon.installDate = aOldAddon.installDate;
|
||||||
|
aNewAddon.applyBackgroundUpdates = aOldAddon.applyBackgroundUpdates;
|
||||||
this.addAddonMetadata(aNewAddon, aDescriptor);
|
this.addAddonMetadata(aNewAddon, aDescriptor);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -3106,29 +3113,16 @@ var XPIDatabase = {
|
||||||
let stmt = this.getStatement("setAddonProperties");
|
let stmt = this.getStatement("setAddonProperties");
|
||||||
stmt.params.internal_id = aAddon._internal_id;
|
stmt.params.internal_id = aAddon._internal_id;
|
||||||
|
|
||||||
if ("userDisabled" in aProperties) {
|
["userDisabled", "appDisabled", "pendingUninstall",
|
||||||
stmt.params.userDisabled = convertBoolean(aProperties.userDisabled);
|
"applyBackgroundUpdates"].forEach(function(aProp) {
|
||||||
aAddon.userDisabled = aProperties.userDisabled;
|
if (aProp in aProperties) {
|
||||||
}
|
stmt.params[aProp] = convertBoolean(aProperties[aProp]);
|
||||||
else {
|
aAddon[aProp] = aProperties[aProp];
|
||||||
stmt.params.userDisabled = convertBoolean(aAddon.userDisabled);
|
}
|
||||||
}
|
else {
|
||||||
|
stmt.params[aProp] = convertBoolean(aAddon[aProp]);
|
||||||
if ("appDisabled" in aProperties) {
|
}
|
||||||
stmt.params.appDisabled = convertBoolean(aProperties.appDisabled);
|
});
|
||||||
aAddon.appDisabled = aProperties.appDisabled;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
stmt.params.appDisabled = convertBoolean(aAddon.appDisabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("pendingUninstall" in aProperties) {
|
|
||||||
stmt.params.pendingUninstall = convertBoolean(aProperties.pendingUninstall);
|
|
||||||
aAddon.pendingUninstall = aProperties.pendingUninstall;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
stmt.params.pendingUninstall = convertBoolean(aAddon.pendingUninstall);
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt.execute();
|
stmt.execute();
|
||||||
},
|
},
|
||||||
|
@ -3822,7 +3816,6 @@ AddonInstall.prototype = {
|
||||||
this.addon.updateDate = dir.lastModifiedTime;
|
this.addon.updateDate = dir.lastModifiedTime;
|
||||||
this.addon.visible = true;
|
this.addon.visible = true;
|
||||||
if (isUpgrade) {
|
if (isUpgrade) {
|
||||||
this.addon.installDate = this.existingAddon.installDate;
|
|
||||||
XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon,
|
XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon,
|
||||||
dir.persistentDescriptor);
|
dir.persistentDescriptor);
|
||||||
}
|
}
|
||||||
|
@ -4314,12 +4307,13 @@ function AddonWrapper(aAddon) {
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|
||||||
this.__defineGetter__("updateAutomatically", function() {
|
this.__defineGetter__("applyBackgroundUpdates", function() {
|
||||||
return aAddon.updateAutomatically;
|
return aAddon.applyBackgroundUpdates;
|
||||||
});
|
});
|
||||||
this.__defineSetter__("updateAutomatically", function(val) {
|
this.__defineSetter__("applyBackgroundUpdates", function(val) {
|
||||||
// TODO store this in the DB (bug 557849)
|
XPIDatabase.setAddonProperties(aAddon, {
|
||||||
aAddon.updateAutomatically = val;
|
applyBackgroundUpdates: val
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.__defineGetter__("install", function() {
|
this.__defineGetter__("install", function() {
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
|
||||||
|
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||||
|
|
||||||
|
<Description about="urn:mozilla:install-manifest">
|
||||||
|
<em:id>addon8@tests.mozilla.org</em:id>
|
||||||
|
<em:version>2.0</em:version>
|
||||||
|
|
||||||
|
<!-- Front End MetaData -->
|
||||||
|
<em:name>Test 8</em:name>
|
||||||
|
<em:description>Test Description</em:description>
|
||||||
|
|
||||||
|
<em:targetApplication>
|
||||||
|
<Description>
|
||||||
|
<em:id>xpcshell@tests.mozilla.org</em:id>
|
||||||
|
<em:minVersion>1</em:minVersion>
|
||||||
|
<em:maxVersion>1</em:maxVersion>
|
||||||
|
</Description>
|
||||||
|
</em:targetApplication>
|
||||||
|
|
||||||
|
</Description>
|
||||||
|
</RDF>
|
|
@ -123,4 +123,24 @@
|
||||||
</Seq>
|
</Seq>
|
||||||
</em:updates>
|
</em:updates>
|
||||||
</Description>
|
</Description>
|
||||||
|
|
||||||
|
<Description about="urn:mozilla:extension:addon8@tests.mozilla.org">
|
||||||
|
<em:updates>
|
||||||
|
<Seq>
|
||||||
|
<li>
|
||||||
|
<Description>
|
||||||
|
<em:version>2.0</em:version>
|
||||||
|
<em:targetApplication>
|
||||||
|
<Description>
|
||||||
|
<em:id>xpcshell@tests.mozilla.org</em:id>
|
||||||
|
<em:minVersion>1</em:minVersion>
|
||||||
|
<em:maxVersion>1</em:maxVersion>
|
||||||
|
<em:updateLink>http://localhost:4444/addons/test_update8.xpi</em:updateLink>
|
||||||
|
</Description>
|
||||||
|
</em:targetApplication>
|
||||||
|
</Description>
|
||||||
|
</li>
|
||||||
|
</Seq>
|
||||||
|
</em:updates>
|
||||||
|
</Description>
|
||||||
</RDF>
|
</RDF>
|
||||||
|
|
|
@ -84,6 +84,8 @@ function run_test_1() {
|
||||||
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
|
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
|
||||||
do_check_neq(a1, null);
|
do_check_neq(a1, null);
|
||||||
do_check_eq(a1.version, "1.0");
|
do_check_eq(a1.version, "1.0");
|
||||||
|
do_check_true(a1.applyBackgroundUpdates);
|
||||||
|
a1.applyBackgroundUpdates = false;
|
||||||
|
|
||||||
prepare_test({}, [
|
prepare_test({}, [
|
||||||
"onNewInstall",
|
"onNewInstall",
|
||||||
|
@ -147,6 +149,7 @@ function check_test_2() {
|
||||||
do_check_neq(a1, null);
|
do_check_neq(a1, null);
|
||||||
do_check_eq(a1.version, "2.0");
|
do_check_eq(a1.version, "2.0");
|
||||||
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
|
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
|
||||||
|
do_check_false(a1.applyBackgroundUpdates);
|
||||||
a1.uninstall();
|
a1.uninstall();
|
||||||
restartManager(0);
|
restartManager(0);
|
||||||
|
|
||||||
|
@ -577,10 +580,22 @@ function run_test_8() {
|
||||||
function([a1, a2, a3, a4, a5, a6]) {
|
function([a1, a2, a3, a4, a5, a6]) {
|
||||||
let count = 6;
|
let count = 6;
|
||||||
|
|
||||||
|
function run_next_test() {
|
||||||
|
a1.uninstall();
|
||||||
|
a2.uninstall();
|
||||||
|
a3.uninstall();
|
||||||
|
a4.uninstall();
|
||||||
|
a5.uninstall();
|
||||||
|
a6.uninstall();
|
||||||
|
|
||||||
|
restartManager(0);
|
||||||
|
run_test_9();
|
||||||
|
}
|
||||||
|
|
||||||
let compatListener = {
|
let compatListener = {
|
||||||
onUpdateFinished: function(addon, error) {
|
onUpdateFinished: function(addon, error) {
|
||||||
if (--count == 0)
|
if (--count == 0)
|
||||||
end_test();
|
run_next_test();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -590,19 +605,8 @@ function run_test_8() {
|
||||||
},
|
},
|
||||||
|
|
||||||
onUpdateFinished: function(addon, error) {
|
onUpdateFinished: function(addon, error) {
|
||||||
if (--count != 0)
|
if (--count == 0)
|
||||||
return;
|
run_next_test();
|
||||||
|
|
||||||
a1.uninstall();
|
|
||||||
a2.uninstall();
|
|
||||||
a3.uninstall();
|
|
||||||
a4.uninstall();
|
|
||||||
a5.uninstall();
|
|
||||||
a6.uninstall();
|
|
||||||
|
|
||||||
restartManager(0);
|
|
||||||
|
|
||||||
run_test_9();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -742,6 +746,90 @@ function check_test_13() {
|
||||||
a7.uninstall();
|
a7.uninstall();
|
||||||
restartManager(0);
|
restartManager(0);
|
||||||
|
|
||||||
|
run_test_14();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that background update checks doesn't update an add-on that isn't
|
||||||
|
// allowed to update automatically.
|
||||||
|
function run_test_14() {
|
||||||
|
// Have an add-on there that will be updated so we see some events from it
|
||||||
|
var dest = profileDir.clone();
|
||||||
|
dest.append("addon1@tests.mozilla.org");
|
||||||
|
writeInstallRDFToDir({
|
||||||
|
id: "addon1@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
updateURL: "http://localhost:4444/data/test_update.rdf",
|
||||||
|
targetApplications: [{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1"
|
||||||
|
}],
|
||||||
|
name: "Test Addon 1",
|
||||||
|
}, dest);
|
||||||
|
|
||||||
|
dest = profileDir.clone();
|
||||||
|
dest.append("addon8@tests.mozilla.org");
|
||||||
|
writeInstallRDFToDir({
|
||||||
|
id: "addon8@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
updateURL: "http://localhost:4444/data/test_update.rdf",
|
||||||
|
targetApplications: [{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1"
|
||||||
|
}],
|
||||||
|
name: "Test Addon 8",
|
||||||
|
}, dest);
|
||||||
|
restartManager(1);
|
||||||
|
|
||||||
|
AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) {
|
||||||
|
a8.applyBackgroundUpdates = false;
|
||||||
|
|
||||||
|
// Note that the background check will find a new update for both add-ons
|
||||||
|
// but only start installing one of them
|
||||||
|
prepare_test({}, [
|
||||||
|
"onNewInstall",
|
||||||
|
"onDownloadStarted",
|
||||||
|
"onNewInstall",
|
||||||
|
"onDownloadEnded"
|
||||||
|
], continue_test_14);
|
||||||
|
|
||||||
|
// Fake a timer event
|
||||||
|
gInternalManager.notify(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function continue_test_14(install) {
|
||||||
|
do_check_neq(install.existingAddon, null);
|
||||||
|
do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org");
|
||||||
|
|
||||||
|
prepare_test({
|
||||||
|
"addon1@tests.mozilla.org": [
|
||||||
|
"onInstalling"
|
||||||
|
]
|
||||||
|
}, [
|
||||||
|
"onInstallStarted",
|
||||||
|
"onInstallEnded",
|
||||||
|
], check_test_14);
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_test_14(install) {
|
||||||
|
do_check_eq(install.existingAddon.pendingUpgrade.install, install);
|
||||||
|
|
||||||
|
restartManager(1);
|
||||||
|
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
|
||||||
|
"addon8@tests.mozilla.org"], function([a1, a8]) {
|
||||||
|
do_check_neq(a1, null);
|
||||||
|
do_check_eq(a1.version, "2.0");
|
||||||
|
a1.uninstall();
|
||||||
|
|
||||||
|
do_check_neq(a8, null);
|
||||||
|
do_check_eq(a8.version, "1.0");
|
||||||
|
a8.uninstall();
|
||||||
|
|
||||||
|
restartManager(0);
|
||||||
|
|
||||||
end_test();
|
end_test();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче