Simplify game files to automatic T5 and T6 uploads
This commit is contained in:
@@ -50,10 +50,10 @@ function operations(id: string) {
|
||||
return hosts.listHostOperations(id).filter(operation => ['runtime.pull', 'library.download'].includes(operation.kind));
|
||||
}
|
||||
|
||||
async function publish(name = 'T5 files', version = '1', game: 't5' | 't6' = 't5', bytes = content) {
|
||||
async function publish(game: 't5' | 't6' = 't5', bytes = content) {
|
||||
vi.setSystemTime(Date.now() + 1);
|
||||
const upload = libraries.createLibraryUpload({
|
||||
name, version, game, files: [{ path: 'zone/game.ff', size: bytes.length, lastModified: 0 }]
|
||||
game, files: [{ path: 'zone/game.ff', size: bytes.length, lastModified: 0 }]
|
||||
});
|
||||
await libraries.uploadBrowserFile(upload.id, 'zone/game.ff', {
|
||||
offset: 0, size: bytes.length, sha256: createHash('sha256').update(bytes).digest('hex')
|
||||
@@ -177,17 +177,16 @@ describe('automatic host preparation', () => {
|
||||
expect(operations(id)).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('downloads the newest published version of every library, including separate libraries for the same game', async () => {
|
||||
const old = await publish('T5 files', 'old');
|
||||
const latest = await publish('T5 files', 'new');
|
||||
const alternate = await publish('T5 alternate');
|
||||
const t6 = await publish('T6 files', '1', 't6');
|
||||
it('always targets the latest verified T5 and T6 upload while newer uploads remain unfinished', async () => {
|
||||
const old = await publish();
|
||||
const latest = await publish();
|
||||
const t6 = await publish('t6');
|
||||
libraries.createLibraryUpload({
|
||||
name: 'T5 files', version: 'unfinished', game: 't5',
|
||||
game: 't5',
|
||||
files: [{ path: 'zone/game.ff', size: content.length, lastModified: 0 }]
|
||||
});
|
||||
const id = enroll({ runtimeImages: [runtime] });
|
||||
const expected = new Map([latest, alternate, t6].map(version => [version.id, version]));
|
||||
const expected = new Map([latest, t6].map(version => [version.id, version]));
|
||||
const downloaded: string[] = [];
|
||||
for (let index = 0; index < expected.size; index++) {
|
||||
preparation.reconcileHostPreparation(id);
|
||||
@@ -218,7 +217,7 @@ describe('automatic host preparation', () => {
|
||||
verified(id, old);
|
||||
heartbeat(id, { runtimeImages: [runtime], libraries: [{ id: old.id, state: 'ready' }] });
|
||||
expect(preparation.reconcileHostPreparation(id).state).toBe('ready');
|
||||
const latest = await publish('T5 files', '2');
|
||||
const latest = await publish();
|
||||
preparation.reconcileHostPreparation(id);
|
||||
expect(operations(id)).toHaveLength(1);
|
||||
expect(operations(id)[0].payload.libraryVersionId).toBe(latest.id);
|
||||
@@ -226,6 +225,41 @@ describe('automatic host preparation', () => {
|
||||
expect(libraries.getHostLibraryCache(id, old.id)?.status).toBe('ready');
|
||||
});
|
||||
|
||||
it('replaces an undelivered automatic download when newer files finish uploading', async () => {
|
||||
const old = await publish();
|
||||
const id = enroll({ runtimeImages: [runtime] });
|
||||
preparation.reconcileHostPreparation(id);
|
||||
const first = operations(id)[0];
|
||||
expect(first.payload).toMatchObject({ libraryVersionId: old.id, automatic: true });
|
||||
// The next heartbeat may discover a completed cache while this queued
|
||||
// retry has still never been delivered. Keep that verified evidence.
|
||||
verified(id, old);
|
||||
const latest = await publish();
|
||||
preparation.reconcileHostPreparation(id);
|
||||
expect(hosts.getHostOperation(first.id)).toMatchObject({ state: 'failed', error: expect.stringContaining('Superseded') });
|
||||
expect(operations(id).filter(operation => operation.state === 'pending')).toMatchObject([
|
||||
{ payload: { libraryVersionId: latest.id, automatic: true } }
|
||||
]);
|
||||
expect(libraries.requireReadyLibrary(id, old.id).id).toBe(old.id);
|
||||
});
|
||||
|
||||
it('finishes a delivered download before following a newer upload under a new operation ID', async () => {
|
||||
const old = await publish();
|
||||
const id = enroll({ runtimeImages: [runtime] });
|
||||
preparation.reconcileHostPreparation(id);
|
||||
const [first] = hosts.pendingHostOperations(id);
|
||||
const latest = await publish();
|
||||
preparation.reconcileHostPreparation(id);
|
||||
expect(operations(id)).toHaveLength(1);
|
||||
expect(hosts.getHostOperation(first.id)?.state).toBe('pending');
|
||||
verified(id, old);
|
||||
hosts.reportHostOperation(id, first.id, { state: 'completed' });
|
||||
heartbeat(id, { runtimeImages: [runtime], libraries: [{ id: old.id, state: 'ready' }] });
|
||||
preparation.reconcileHostPreparation(id);
|
||||
expect(operations(id)[0]).toMatchObject({ state: 'pending', payload: { libraryVersionId: latest.id } });
|
||||
expect(libraries.requireReadyLibrary(id, old.id).id).toBe(old.id);
|
||||
});
|
||||
|
||||
it.each(['draining', 'disabled'] as const)('does not start setup for a %s host', mode => {
|
||||
const id = enroll();
|
||||
hosts.updateHost(id, { mode });
|
||||
@@ -305,15 +339,15 @@ describe('automatic host preparation', () => {
|
||||
expect(operations(id)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('lets other libraries prepare when an earlier library cannot fit', async () => {
|
||||
const oversized = await publish('A oversized');
|
||||
const small = await publish('B small', '1', 't6', 'small');
|
||||
it('prepares T6 when the latest T5 upload cannot fit', async () => {
|
||||
const oversized = await publish();
|
||||
const small = await publish('t6', 'small');
|
||||
const id = enroll({ runtimeImages: [runtime], storageAvailable: 1_010 });
|
||||
hosts.updateHost(id, { minimumFreeBytes: 1_000 });
|
||||
const status = preparation.reconcileHostPreparation(id);
|
||||
expect(status.items).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ label: 'A oversized · 1', state: 'blocked' }),
|
||||
expect.objectContaining({ label: 'B small · 1', state: 'preparing' })
|
||||
expect.objectContaining({ label: 'Black Ops I (T5)', state: 'blocked' }),
|
||||
expect.objectContaining({ label: 'Black Ops II (T6)', state: 'preparing' })
|
||||
]));
|
||||
expect(operations(id)).toHaveLength(1);
|
||||
expect(operations(id)[0].payload.libraryVersionId).toBe(small.id);
|
||||
|
||||
Reference in New Issue
Block a user