Skip to content

Commit be948e9

Browse files
authored
Improved MySQL acceptance test resets (#28554)
no ref - Cache the initialized MySQL acceptance-test database into in-database snapshot tables after the first truncating reset. - Restore subsequent MySQL resets from that snapshot instead of rerunning `knexMigrator.init({only: 3})` for every Ghost boot. - Invalidate and clean snapshot tables when using the full reset/teardown paths, with fallback to the existing slow-safe reinitialization path. - Move the older `e2e-utils` boot helper onto the truncating reset path so it benefits from the same reset behavior.
1 parent 4250b43 commit be948e9

2 files changed

Lines changed: 98 additions & 4 deletions

File tree

ghost/core/test/utils/db-utils.js

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ const {sequence} = require('@tryghost/promise');
2222
const urlServiceUtils = require('./url-service-utils');
2323

2424
let dbInitialized = false;
25+
let mysqlSnapshotDatabase = null;
26+
const mysqlSnapshotTablePrefix = '__ghost_snapshot_';
2527

2628
/**
2729
* Checks if the current active connection is a MySQL database
@@ -69,11 +71,11 @@ module.exports.reset = async ({truncate} = {truncate: false}) => {
6971
if (truncate) {
7072
// Perform a fast reset by tearing down all the tables and inserting the fixtures
7173
try {
72-
await truncateAll();
73-
await knexMigrator.init({only: 3});
74+
await resetMySQLFromSnapshot();
7475
} catch (err) {
7576
// If it fails, try a normal restore
7677
await forceReinit();
78+
await createMySQLSnapshot();
7779
}
7880
} else {
7981
// Do a full database reset + initialisation
@@ -93,6 +95,8 @@ module.exports.teardown = async () => {
9395
} catch (err) {
9496
await knexMigrator.reset({force: true});
9597
}
98+
99+
await dropMySQLSnapshots();
96100
};
97101

98102
/**
@@ -124,6 +128,96 @@ module.exports.truncate = async (tableName) => {
124128
const forceReinit = async () => {
125129
await knexMigrator.reset({force: true});
126130
await knexMigrator.init();
131+
await dropMySQLSnapshots();
132+
};
133+
134+
const getResetTables = () => {
135+
return schemaTables.concat(['migrations']);
136+
};
137+
138+
const getMySQLSnapshotTableName = (table) => {
139+
return `${mysqlSnapshotTablePrefix}${table}`;
140+
};
141+
142+
const getMySQLDatabaseName = () => {
143+
return config.get('database:connection:database');
144+
};
145+
146+
const isMySQLSnapshotCurrent = () => {
147+
return mysqlSnapshotDatabase === getMySQLDatabaseName();
148+
};
149+
150+
const resetMySQLFromSnapshot = async () => {
151+
if (!isMySQLSnapshotCurrent()) {
152+
await truncateAll();
153+
await knexMigrator.init({only: 3});
154+
await createMySQLSnapshot();
155+
return;
156+
}
157+
158+
await restoreMySQLSnapshot();
159+
};
160+
161+
const createMySQLSnapshot = async () => {
162+
if (!module.exports.isMySQL()) {
163+
return;
164+
}
165+
166+
const tables = getResetTables();
167+
168+
await sequence(tables.map(table => async () => {
169+
const snapshotTable = getMySQLSnapshotTableName(table);
170+
171+
await db.knex.schema.dropTableIfExists(snapshotTable);
172+
await db.knex.raw('CREATE TABLE ?? LIKE ??', [snapshotTable, table]);
173+
await db.knex.raw('INSERT INTO ?? SELECT * FROM ??', [snapshotTable, table]);
174+
}));
175+
176+
mysqlSnapshotDatabase = getMySQLDatabaseName();
177+
};
178+
179+
const restoreMySQLSnapshot = async () => {
180+
debug('Database snapshot restore');
181+
urlServiceUtils.reset();
182+
183+
const tables = getResetTables();
184+
185+
await db.knex.transaction(async (trx) => {
186+
try {
187+
await db.knex.raw('SET FOREIGN_KEY_CHECKS=0;').transacting(trx);
188+
189+
await sequence(tables.map(table => async () => {
190+
const snapshotTable = getMySQLSnapshotTableName(table);
191+
192+
await db.knex.raw('DELETE FROM ??', [table]).transacting(trx);
193+
await db.knex.raw('INSERT INTO ?? SELECT * FROM ??', [table, snapshotTable]).transacting(trx);
194+
}));
195+
} finally {
196+
await db.knex.raw('SET FOREIGN_KEY_CHECKS=1;').transacting(trx);
197+
debug('Database snapshot restore end');
198+
}
199+
});
200+
};
201+
202+
const dropMySQLSnapshots = async () => {
203+
if (!module.exports.isMySQL()) {
204+
return;
205+
}
206+
207+
mysqlSnapshotDatabase = null;
208+
209+
try {
210+
await sequence(getResetTables().map(table => () => {
211+
return db.knex.schema.dropTableIfExists(getMySQLSnapshotTableName(table));
212+
}));
213+
} catch (err) {
214+
// CASE: table does not exist || DB does not exist
215+
if (err.errno === 1146 || err.errno === 1049) {
216+
return Promise.resolve();
217+
}
218+
219+
throw err;
220+
}
127221
};
128222

129223
/**
@@ -135,7 +229,7 @@ const truncateAll = async () => {
135229
debug('Database teardown');
136230
urlServiceUtils.reset();
137231

138-
const tables = schemaTables.concat(['migrations']);
232+
const tables = getResetTables();
139233

140234
if (module.exports.isSQLite()) {
141235
try {

ghost/core/test/utils/e2e-utils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ const _startGhost = async (options) => {
105105
// Reset the settings cache and disable listeners so they don't get triggered further
106106
settingsService.reset();
107107

108-
await dbUtils.reset();
108+
await dbUtils.reset({truncate: true});
109109

110110
await settingsService.init();
111111

0 commit comments

Comments
 (0)