/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import * as fs from 'fs';
import { deepStrictEqual, ok, strictEqual } from 'assert';
import { tmpdir } from 'os';
import { timeout } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { join } from 'vs/base/common/path';
import { isWindows } from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import { Promises } from 'vs/base/node/pfs';
import { isStorageItemsChangeEvent, IStorageDatabase, IStorageItemsChangeEvent, Storage } from 'vs/base/parts/storage/common/storage';
import { ISQLiteStorageDatabaseOptions, SQLiteStorageDatabase } from 'vs/base/parts/storage/node/storage';
import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler';
import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils';

flakySuite('Storage Library', function () {

	let testDir: string;

	setup(function () {
		testDir = getRandomTestPath(tmpdir(), 'vsctests', 'storagelibrary');

		return fs.promises.mkdir(testDir, { recursive: true });
	});

	teardown(function () {
		return Promises.rm(testDir);
	});

	test('objects', () => {
		return runWithFakedTimers({}, async function () {
			const storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db')));

			await storage.init();

			ok(!storage.getObject('foo'));
			const uri = URI.file('path/to/folder');
			storage.set('foo', { 'bar': uri });
			deepStrictEqual(storage.getObject('foo'), { 'bar': uri });

			await storage.close();
		});
	});

	test('basics', () => {
		return runWithFakedTimers({}, async function () {
			const storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db')));

			await storage.init();

			// Empty fallbacks
			strictEqual(storage.get('foo', 'bar'), 'bar');
			strictEqual(storage.getNumber('foo', 55), 55);
			strictEqual(storage.getBoolean('foo', true), true);
			deepStrictEqual(storage.getObject('foo', { 'bar': 'baz' }), { 'bar': 'baz' });

			let changes = new Set<string>();
			storage.onDidChangeStorage(e => {
				changes.add(e.key);
			});

			await storage.whenFlushed(); // returns immediately when no pending updates

			// Simple updates
			const set1Promise = storage.set('bar', 'foo');
			const set2Promise = storage.set('barNumber', 55);
			const set3Promise = storage.set('barBoolean', true);
			const set4Promise = storage.set('barObject', { 'bar': 'baz' });

			let flushPromiseResolved = false;
			storage.whenFlushed().then(() => flushPromiseResolved = true);

			strictEqual(storage.get('bar'), 'foo');
			strictEqual(storage.getNumber('barNumber'), 55);
			strictEqual(storage.getBoolean('barBoolean'), true);
			deepStrictEqual(storage.getObject('barObject'), { 'bar': 'baz' });

			strictEqual(changes.size, 4);
			ok(changes.has('bar'));
			ok(changes.has('barNumber'));
			ok(changes.has('barBoolean'));
			ok(changes.has('barObject'));

			let setPromiseResolved = false;
			await Promise.all([set1Promise, set2Promise, set3Promise, set4Promise]).then(() => setPromiseResolved = true);
			strictEqual(setPromiseResolved, true);
			strictEqual(flushPromiseResolved, true);

			changes = new Set<string>();

			// Does not trigger events for same update values
			storage.set('bar', 'foo');
			storage.set('barNumber', 55);
			storage.set('barBoolean', true);
			storage.set('barObject', { 'bar': 'baz' });
			strictEqual(changes.size, 0);

			// Simple deletes
			const delete1Promise = storage.delete('bar');
			const delete2Promise = storage.delete('barNumber');
			const delete3Promise = storage.delete('barBoolean');
			const delete4Promise = storage.delete('barObject');

			ok(!storage.get('bar'));
			ok(!storage.getNumber('barNumber'));
			ok(!storage.getBoolean('barBoolean'));
			ok(!storage.getObject('barObject'));

			strictEqual(changes.size, 4);
			ok(changes.has('bar'));
			ok(changes.has('barNumber'));
			ok(changes.has('barBoolean'));
			ok(changes.has('barObject'));

			changes = new Set<string>();

			// Does not trigger events for same delete values
			storage.delete('bar');
			storage.delete('barNumber');
			storage.delete('barBoolean');
			storage.delete('barObject');
			strictEqual(changes.size, 0);

			let deletePromiseResolved = false;
			await Promise.all([delete1Promise, delete2Promise, delete3Promise, delete4Promise]).then(() => deletePromiseResolved = true);
			strictEqual(deletePromiseResolved, true);

			await storage.close();
			await storage.close(); // it is ok to call this multiple times
		});
	});

	test('external changes', () => {
		return runWithFakedTimers({}, async function () {
			class TestSQLiteStorageDatabase extends SQLiteStorageDatabase {
				private readonly _onDidChangeItemsExternal = new Emitter<IStorageItemsChangeEvent>();
				override get onDidChangeItemsExternal(): Event<IStorageItemsChangeEvent> { return this._onDidChangeItemsExternal.event; }

				fireDidChangeItemsExternal(event: IStorageItemsChangeEvent): void {
					this._onDidChangeItemsExternal.fire(event);
				}
			}

			const database = new TestSQLiteStorageDatabase(join(testDir, 'storage.db'));
			const storage = new Storage(database);

			const changes = new Set<string>();
			storage.onDidChangeStorage(e => {
				changes.add(e.key);
			});

			await storage.init();

			await storage.set('foo', 'bar');
			ok(changes.has('foo'));
			changes.clear();

			// Nothing happens if changing to same value
			const changed = new Map<string, string>();
			changed.set('foo', 'bar');
			database.fireDidChangeItemsExternal({ changed });
			strictEqual(changes.size, 0);

			// Change is accepted if valid
			changed.set('foo', 'bar1');
			database.fireDidChangeItemsExternal({ changed });
			ok(changes.has('foo'));
			strictEqual(storage.get('foo'), 'bar1');
			changes.clear();

			// Delete is accepted
			const deleted = new Set<string>(['foo']);
			database.fireDidChangeItemsExternal({ deleted });
			ok(changes.has('foo'));
			strictEqual(storage.get('foo', undefined), undefined);
			changes.clear();

			// Nothing happens if changing to same value
			database.fireDidChangeItemsExternal({ deleted });
			strictEqual(changes.size, 0);

			strictEqual(isStorageItemsChangeEvent({ changed }), true);
			strictEqual(isStorageItemsChangeEvent({ deleted }), true);
			strictEqual(isStorageItemsChangeEvent({ changed, deleted }), true);
			strictEqual(isStorageItemsChangeEvent(undefined), false);
			strictEqual(isStorageItemsChangeEvent({ changed: 'yes', deleted: false }), false);

			await storage.close();
		});
	});

	test('close flushes data', async () => {
		let storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db')));
		await storage.init();

		const set1Promise = storage.set('foo', 'bar');
		const set2Promise = storage.set('bar', 'foo');

		let flushPromiseResolved = false;
		storage.whenFlushed().then(() => flushPromiseResolved = true);

		strictEqual(storage.get('foo'), 'bar');
		strictEqual(storage.get('bar'), 'foo');

		let setPromiseResolved = false;
		Promise.all([set1Promise, set2Promise]).then(() => setPromiseResolved = true);

		await storage.close();

		strictEqual(setPromiseResolved, true);
		strictEqual(flushPromiseResolved, true);

		storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db')));
		await storage.init();

		strictEqual(storage.get('foo'), 'bar');
		strictEqual(storage.get('bar'), 'foo');

		await storage.close();

		storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db')));
		await storage.init();

		const delete1Promise = storage.delete('foo');
		const delete2Promise = storage.delete('bar');

		ok(!storage.get('foo'));
		ok(!storage.get('bar'));

		let deletePromiseResolved = false;
		Promise.all([delete1Promise, delete2Promise]).then(() => deletePromiseResolved = true);

		await storage.close();

		strictEqual(deletePromiseResolved, true);

		storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db')));
		await storage.init();

		ok(!storage.get('foo'));
		ok(!storage.get('bar'));

		await storage.close();
	});

	test('explicit flush', async () => {
		const storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db')));
		await storage.init();

		storage.set('foo', 'bar');
		storage.set('bar', 'foo');

		let flushPromiseResolved = false;
		storage.whenFlushed().then(() => flushPromiseResolved = true);

		strictEqual(flushPromiseResolved, false);

		await storage.flush(0);

		strictEqual(flushPromiseResolved, true);

		await storage.close();
	});

	test('conflicting updates', () => {
		return runWithFakedTimers({}, async function () {
			const storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db')));
			await storage.init();

			let changes = new Set<string>();
			storage.onDidChangeStorage(e => {
				changes.add(e.key);
			});

			const set1Promise = storage.set('foo', 'bar1');
			const set2Promise = storage.set('foo', 'bar2');
			const set3Promise = storage.set('foo', 'bar3');

			let flushPromiseResolved = false;
			storage.whenFlushed().then(() => flushPromiseResolved = true);

			strictEqual(storage.get('foo'), 'bar3');
			strictEqual(changes.size, 1);
			ok(changes.has('foo'));

			let setPromiseResolved = false;
			await Promise.all([set1Promise, set2Promise, set3Promise]).then(() => setPromiseResolved = true);
			ok(setPromiseResolved);
			ok(flushPromiseResolved);

			changes = new Set<string>();

			const set4Promise = storage.set('bar', 'foo');
			const delete1Promise = storage.delete('bar');

			ok(!storage.get('bar'));

			strictEqual(changes.size, 1);
			ok(changes.has('bar'));

			let setAndDeletePromiseResolved = false;
			await Promise.all([set4Promise, delete1Promise]).then(() => setAndDeletePromiseResolved = true);
			ok(setAndDeletePromiseResolved);

			await storage.close();
		});
	});

	test('corrupt DB recovers', async () => {
		return runWithFakedTimers({}, async function () {
			const storageFile = join(testDir, 'storage.db');

			let storage = new Storage(new SQLiteStorageDatabase(storageFile));
			await storage.init();

			await storage.set('bar', 'foo');

			await Promises.writeFile(storageFile, 'This is a broken DB');

			await storage.set('foo', 'bar');

			strictEqual(storage.get('bar'), 'foo');
			strictEqual(storage.get('foo'), 'bar');

			await storage.close();

			storage = new Storage(new SQLiteStorageDatabase(storageFile));
			await storage.init();

			strictEqual(storage.get('bar'), 'foo');
			strictEqual(storage.get('foo'), 'bar');

			await storage.close();
		});
	});
});

flakySuite('SQLite Storage Library', function () {

	function toSet(elements: string[]): Set<string> {
		const set = new Set<string>();
		elements.forEach(element => set.add(element));

		return set;
	}

	let testdir: string;

	setup(function () {
		testdir = getRandomTestPath(tmpdir(), 'vsctests', 'storagelibrary');

		return fs.promises.mkdir(testdir, { recursive: true });
	});

	teardown(function () {
		return Promises.rm(testdir);
	});

	async function testDBBasics(path: string, logError?: (error: Error | string) => void) {
		let options!: ISQLiteStorageDatabaseOptions;
		if (logError) {
			options = {
				logging: {
					logError
				}
			};
		}

		const storage = new SQLiteStorageDatabase(path, options);

		const items = new Map<string, string>();
		items.set('foo', 'bar');
		items.set('some/foo/path', 'some/bar/path');
		items.set(JSON.stringify({ foo: 'bar' }), JSON.stringify({ bar: 'foo' }));

		let storedItems = await storage.getItems();
		strictEqual(storedItems.size, 0);

		await storage.updateItems({ insert: items });

		storedItems = await storage.getItems();
		strictEqual(storedItems.size, items.size);
		strictEqual(storedItems.get('foo'), 'bar');
		strictEqual(storedItems.get('some/foo/path'), 'some/bar/path');
		strictEqual(storedItems.get(JSON.stringify({ foo: 'bar' })), JSON.stringify({ bar: 'foo' }));

		await storage.updateItems({ delete: toSet(['foo']) });
		storedItems = await storage.getItems();
		strictEqual(storedItems.size, items.size - 1);
		ok(!storedItems.has('foo'));
		strictEqual(storedItems.get('some/foo/path'), 'some/bar/path');
		strictEqual(storedItems.get(JSON.stringify({ foo: 'bar' })), JSON.stringify({ bar: 'foo' }));

		await storage.updateItems({ insert: items });
		storedItems = await storage.getItems();
		strictEqual(storedItems.size, items.size);
		strictEqual(storedItems.get('foo'), 'bar');
		strictEqual(storedItems.get('some/foo/path'), 'some/bar/path');
		strictEqual(storedItems.get(JSON.stringify({ foo: 'bar' })), JSON.stringify({ bar: 'foo' }));

		const itemsChange = new Map<string, string>();
		itemsChange.set('foo', 'otherbar');
		await storage.updateItems({ insert: itemsChange });

		storedItems = await storage.getItems();
		strictEqual(storedItems.get('foo'), 'otherbar');

		await storage.updateItems({ delete: toSet(['foo', 'bar', 'some/foo/path', JSON.stringify({ foo: 'bar' })]) });
		storedItems = await storage.getItems();
		strictEqual(storedItems.size, 0);

		await storage.updateItems({ insert: items, delete: toSet(['foo', 'some/foo/path', 'other']) });
		storedItems = await storage.getItems();
		strictEqual(storedItems.size, 1);
		strictEqual(storedItems.get(JSON.stringify({ foo: 'bar' })), JSON.stringify({ bar: 'foo' }));

		await storage.updateItems({ delete: toSet([JSON.stringify({ foo: 'bar' })]) });
		storedItems = await storage.getItems();
		strictEqual(storedItems.size, 0);

		let recoveryCalled = false;
		await storage.close(() => {
			recoveryCalled = true;

			return new Map();
		});

		strictEqual(recoveryCalled, false);
	}

	test('basics', async () => {
		await testDBBasics(join(testdir, 'storage.db'));
	});

	test('basics (open multiple times)', async () => {
		await testDBBasics(join(testdir, 'storage.db'));
		await testDBBasics(join(testdir, 'storage.db'));
	});

	test('basics (corrupt DB falls back to empty DB)', async () => {
		const corruptDBPath = join(testdir, 'broken.db');
		await Promises.writeFile(corruptDBPath, 'This is a broken DB');

		let expectedError: any;
		await testDBBasics(corruptDBPath, error => {
			expectedError = error;
		});

		ok(expectedError);
	});

	test('basics (corrupt DB restores from previous backup)', async () => {
		const storagePath = join(testdir, 'storage.db');
		let storage = new SQLiteStorageDatabase(storagePath);

		const items = new Map<string, string>();
		items.set('foo', 'bar');
		items.set('some/foo/path', 'some/bar/path');
		items.set(JSON.stringify({ foo: 'bar' }), JSON.stringify({ bar: 'foo' }));

		await storage.updateItems({ insert: items });
		await storage.close();

		await Promises.writeFile(storagePath, 'This is now a broken DB');

		storage = new SQLiteStorageDatabase(storagePath);

		const storedItems = await storage.getItems();
		strictEqual(storedItems.size, items.size);
		strictEqual(storedItems.get('foo'), 'bar');
		strictEqual(storedItems.get('some/foo/path'), 'some/bar/path');
		strictEqual(storedItems.get(JSON.stringify({ foo: 'bar' })), JSON.stringify({ bar: 'foo' }));

		let recoveryCalled = false;
		await storage.close(() => {
			recoveryCalled = true;

			return new Map();
		});

		strictEqual(recoveryCalled, false);
	});

	test('basics (corrupt DB falls back to empty DB if backup is corrupt)', async () => {
		const storagePath = join(testdir, 'storage.db');
		let storage = new SQLiteStorageDatabase(storagePath);

		const items = new Map<string, string>();
		items.set('foo', 'bar');
		items.set('some/foo/path', 'some/bar/path');
		items.set(JSON.stringify({ foo: 'bar' }), JSON.stringify({ bar: 'foo' }));

		await storage.updateItems({ insert: items });
		await storage.close();

		await Promises.writeFile(storagePath, 'This is now a broken DB');
		await Promises.writeFile(`${storagePath}.backup`, 'This is now also a broken DB');

		storage = new SQLiteStorageDatabase(storagePath);

		const storedItems = await storage.getItems();
		strictEqual(storedItems.size, 0);

		await testDBBasics(storagePath);
	});

	(isWindows ? test.skip /* Windows will fail to write to open DB due to locking */ : test)('basics (DB that becomes corrupt during runtime stores all state from cache on close)', async () => {
		const storagePath = join(testdir, 'storage.db');
		let storage = new SQLiteStorageDatabase(storagePath);

		const items = new Map<string, string>();
		items.set('foo', 'bar');
		items.set('some/foo/path', 'some/bar/path');
		items.set(JSON.stringify({ foo: 'bar' }), JSON.stringify({ bar: 'foo' }));

		await storage.updateItems({ insert: items });
		await storage.close();

		const backupPath = `${storagePath}.backup`;
		strictEqual(await Promises.exists(backupPath), true);

		storage = new SQLiteStorageDatabase(storagePath);
		await storage.getItems();

		await Promises.writeFile(storagePath, 'This is now a broken DB');

		// we still need to trigger a check to the DB so that we get to know that
		// the DB is corrupt. We have no extra code on shutdown that checks for the
		// health of the DB. This is an optimization to not perform too many tasks
		// on shutdown.
		await storage.checkIntegrity(true).then(null, error => { } /* error is expected here but we do not want to fail */);

		await fs.promises.unlink(backupPath); // also test that the recovery DB is backed up properly

		let recoveryCalled = false;
		await storage.close(() => {
			recoveryCalled = true;

			return items;
		});

		strictEqual(recoveryCalled, true);
		strictEqual(await Promises.exists(backupPath), true);

		storage = new SQLiteStorageDatabase(storagePath);

		const storedItems = await storage.getItems();
		strictEqual(storedItems.size, items.size);
		strictEqual(storedItems.get('foo'), 'bar');
		strictEqual(storedItems.get('some/foo/path'), 'some/bar/path');
		strictEqual(storedItems.get(JSON.stringify({ foo: 'bar' })), JSON.stringify({ bar: 'foo' }));

		recoveryCalled = false;
		await storage.close(() => {
			recoveryCalled = true;

			return new Map();
		});

		strictEqual(recoveryCalled, false);
	});

	test('real world example', async function () {
		let storage = new SQLiteStorageDatabase(join(testdir, 'storage.db'));

		const items1 = new Map<string, string>();
		items1.set('colorthemedata', '{"id":"vs vscode-theme-defaults-themes-light_plus-json","label":"Light+ (default light)","settingsId":"Default Light+","selector":"vs.vscode-theme-defaults-themes-light_plus-json","themeTokenColors":[{"settings":{"foreground":"#000000ff","background":"#ffffffff"}},{"scope":["meta.embedded","source.groovy.embedded"],"settings":{"foreground":"#000000ff"}},{"scope":"emphasis","settings":{"fontStyle":"italic"}},{"scope":"strong","settings":{"fontStyle":"bold"}},{"scope":"meta.diff.header","settings":{"foreground":"#000080"}},{"scope":"comment","settings":{"foreground":"#008000"}},{"scope":"constant.language","settings":{"foreground":"#0000ff"}},{"scope":["constant.numeric"],"settings":{"foreground":"#098658"}},{"scope":"constant.regexp","settings":{"foreground":"#811f3f"}},{"name":"css tags in selectors, xml tags","scope":"entity.name.tag","settings":{"foreground":"#800000"}},{"scope":"entity.name.selector","settings":{"foreground":"#800000"}},{"scope":"entity.other.attribute-name","settings":{"foreground":"#ff0000"}},{"scope":["entity.other.attribute-name.class.css","entity.other.attribute-name.class.mixin.css","entity.other.attribute-name.id.css","entity.other.attribute-name.parent-selector.css","entity.other.attribute-name.pseudo-class.css","entity.other.attribute-name.pseudo-element.css","source.css.less entity.other.attribute-name.id","entity.other.attribute-name.attribute.scss","entity.other.attribute-name.scss"],"settings":{"foreground":"#800000"}},{"scope":"invalid","settings":{"foreground":"#cd3131"}},{"scope":"markup.underline","settings":{"fontStyle":"underline"}},{"scope":"markup.bold","settings":{"fontStyle":"bold","foreground":"#000080"}},{"scope":"markup.heading","settings":{"fontStyle":"bold","foreground":"#800000"}},{"scope":"markup.italic","settings":{"fontStyle":"italic"}},{"scope":"markup.inserted","settings":{"foreground":"#098658"}},{"scope":"markup.deleted","settings":{"foreground":"#a31515"}},{"scope":"markup.changed","settings":{"foreground":"#0451a5"}},{"scope":["punctuation.definition.quote.begin.markdown","punctuation.definition.list.begin.markdown"],"settings":{"foreground":"#0451a5"}},{"scope":"markup.inline.raw","settings":{"foreground":"#800000"}},{"name":"brackets of XML/HTML tags","scope":"punctuation.definition.tag","settings":{"foreground":"#800000"}},{"scope":"meta.preprocessor","settings":{"foreground":"#0000ff"}},{"scope":"meta.preprocessor.string","settings":{"foreground":"#a31515"}},{"scope":"meta.preprocessor.numeric","settings":{"foreground":"#098658"}},{"scope":"meta.structure.dictionary.key.python","settings":{"foreground":"#0451a5"}},{"scope":"storage","settings":{"foreground":"#0000ff"}},{"scope":"storage.type","settings":{"foreground":"#0000ff"}},{"scope":"storage.modifier","settings":{"foreground":"#0000ff"}},{"scope":"string","settings":{"foreground":"#a31515"}},{"scope":["string.comment.buffered.block.pug","string.quoted.pug","string.interpolated.pug","string.unquoted.plain.in.yaml","string.unquoted.plain.out.yaml","string.unquoted.block.yaml","string.quoted.single.yaml","string.quoted.double.xml","string.quoted.single.xml","string.unquoted.cdata.xml","string.quoted.double.html","string.quoted.single.html","string.unquoted.html","string.quoted.single.handlebars","string.quoted.double.handlebars"],"settings":{"foreground":"#0000ff"}},{"scope":"string.regexp","settings":{"foreground":"#811f3f"}},{"name":"String interpolation","scope":["punctuation.definition.template-expression.begin","punctuation.definition.template-expression.end","punctuation.section.embedded"],"settings":{"foreground":"#0000ff"}},{"name":"Reset JavaScript string interpolation expression","scope":["meta.template.expression"],"settings":{"foreground":"#000000"}},{"scope":["support.constant.property-value","support.constant.font-name","support.constant.media-type","support.constant.media","constant.other.color.rgb-value","constant.other.rgb-value","support.constant.color"],"settings":{"foreground":"#0451a5"}},{"scope":["support.type.vendored.property-name","support.type.property-name","variable.css","variable.scss","variable.other.less","source.coffee.embedded"],"settings":{"foreground":"#ff0000"}},{"scope":["support.type.property-name.json"],"settings":{"foreground":"#0451a5"}},{"scope":"keyword","settings":{"foreground":"#0000ff"}},{"scope":"keyword.control","settings":{"foreground":"#0000ff"}},{"scope":"keyword.operator","settings":{"foreground":"#000000"}},{"scope":["keyword.operator.new","keyword.operator.expression","keyword.operator.cast","keyword.operator.sizeof","keyword.operator.instanceof","keyword.operator.logical.python"],"settings":{"foreground":"#0000ff"}},{"scope":"keyword.other.unit","settings":{"foreground":"#098658"}},{"scope":["punctuation.section.embedded.begin.php","punctuation.section.embedded.end.php"],"settings":{"foreground":"#800000"}},{"scope":"support.function.git-rebase","settings":{"foreground":"#0451a5"}},{"scope":"constant.sha.git-rebase","settings":{"foreground":"#098658"}},{"name":"coloring of the Java import and package identifiers","scope":["storage.modifier.import.java","variable.language.wildcard.java","storage.modifier.package.java"],"settings":{"foreground":"#000000"}},{"name":"this.self","scope":"variable.language","settings":{"foreground":"#0000ff"}},{"name":"Function declarations","scope":["entity.name.function","support.function","support.constant.handlebars"],"settings":{"foreground":"#795E26"}},{"name":"Types declaration and references","scope":["meta.return-type","support.class","support.type","entity.name.type","entity.name.class","storage.type.numeric.go","storage.type.byte.go","storage.type.boolean.go","storage.type.string.go","storage.type.uintptr.go","storage.type.error.go","storage.type.rune.go","storage.type.cs","storage.type.generic.cs","storage.type.modifier.cs","storage.type.variable.cs","storage.type.annotation.java","storage.type.generic.java","storage.type.java","storage.type.object.array.java","storage.type.primitive.array.java","storage.type.primitive.java","storage.type.token.java","storage.type.groovy","storage.type.annotation.groovy","storage.type.parameters.groovy","storage.type.generic.groovy","storage.type.object.array.groovy","storage.type.primitive.array.groovy","storage.type.primitive.groovy"],"settings":{"foreground":"#267f99"}},{"name":"Types declaration and references, TS grammar specific","scope":["meta.type.cast.expr","meta.type.new.expr","support.constant.math","support.constant.dom","support.constant.json","entity.other.inherited-class"],"settings":{"foreground":"#267f99"}},{"name":"Control flow keywords","scope":"keyword.control","settings":{"foreground":"#AF00DB"}},{"name":"Variable and parameter name","scope":["variable","meta.definition.variable.name","support.variable","entity.name.variable"],"settings":{"foreground":"#001080"}},{"name":"Object keys, TS grammar specific","scope":["meta.object-literal.key"],"settings":{"foreground":"#001080"}},{"name":"CSS property value","scope":["support.constant.property-value","support.constant.font-name","support.constant.media-type","support.constant.media","constant.other.color.rgb-value","constant.other.rgb-value","support.constant.color"],"settings":{"foreground":"#0451a5"}},{"name":"Regular expression groups","scope":["punctuation.definition.group.regexp","punctuation.definition.group.assertion.regexp","punctuation.definition.character-class.regexp","punctuation.character.set.begin.regexp","punctuation.character.set.end.regexp","keyword.operator.negation.regexp","support.other.parenthesis.regexp"],"settings":{"foreground":"#d16969"}},{"scope":["constant.character.character-class.regexp","constant.other.character-class.set.regexp","constant.other.character-class.regexp","constant.character.set.regexp"],"settings":{"foreground":"#811f3f"}},{"scope":"keyword.operator.quantifier.regexp","settings":{"foreground":"#000000"}},{"scope":["keyword.operator.or.regexp","keyword.control.anchor.regexp"],"settings":{"foreground":"#ff0000"}},{"scope":"constant.character","settings":{"foreground":"#0000ff"}},{"scope":"constant.character.escape","settings":{"foreground":"#ff0000"}},{"scope":"token.info-token","settings":{"foreground":"#316bcd"}},{"scope":"token.warn-token","settings":{"foreground":"#cd9731"}},{"scope":"token.error-token","settings":{"foreground":"#cd3131"}},{"scope":"token.debug-token","settings":{"foreground":"#800080"}}],"extensionData":{"extensionId":"vscode.theme-defaults","extensionPublisher":"vscode","extensionName":"theme-defaults","extensionIsBuiltin":true},"colorMap":{"editor.background":"#ffffff","editor.foreground":"#000000","editor.inactiveSelectionBackground":"#e5ebf1","editorIndentGuide.background":"#d3d3d3","editorIndentGuide.activeBackground":"#939393","editor.selectionHighlightBackground":"#add6ff4d","editorSuggestWidget.background":"#f3f3f3","activityBarBadge.background":"#007acc","sideBarTitle.foreground":"#6f6f6f","list.hoverBackground":"#e8e8e8","input.placeholderForeground":"#767676","settings.textInputBorder":"#cecece","settings.numberInputBorder":"#cecece"}}');
		items1.set('commandpalette.mru.cache', '{"usesLRU":true,"entries":[{"key":"revealFileInOS","value":3},{"key":"extension.openInGitHub","value":4},{"key":"workbench.extensions.action.openExtensionsFolder","value":11},{"key":"workbench.action.showRuntimeExtensions","value":14},{"key":"workbench.action.toggleTabsVisibility","value":15},{"key":"extension.liveServerPreview.open","value":16},{"key":"workbench.action.openIssueReporter","value":18},{"key":"workbench.action.openProcessExplorer","value":19},{"key":"workbench.action.toggleSharedProcess","value":20},{"key":"workbench.action.configureLocale","value":21},{"key":"workbench.action.appPerf","value":22},{"key":"workbench.action.reportPerformanceIssueUsingReporter","value":23},{"key":"workbench.action.openGlobalKeybindings","value":25},{"key":"workbench.action.output.toggleOutput","value":27},{"key":"extension.sayHello","value":29}]}');
		items1.set('cpp.1.lastsessiondate', 'Fri Oct 05 2018');
		items1.set('debug.actionswidgetposition', '0.6880952380952381');

		const items2 = new Map<string, string>();
		items2.set('workbench.editors.files.textfileeditor', '{"textEditorViewState":[["file:///Users/dummy/Documents/ticino-playground/play.htm",{"0":{"cursorState":[{"inSelectionMode":false,"selectionStart":{"lineNumber":6,"column":16},"position":{"lineNumber":6,"column":16}}],"viewState":{"scrollLeft":0,"firstPosition":{"lineNumber":1,"column":1},"firstPositionDeltaTop":0},"contributionsState":{"editor.contrib.folding":{},"editor.contrib.wordHighlighter":false}}}],["file:///Users/dummy/Documents/ticino-playground/nakefile.js",{"0":{"cursorState":[{"inSelectionMode":false,"selectionStart":{"lineNumber":7,"column":81},"position":{"lineNumber":7,"column":81}}],"viewState":{"scrollLeft":0,"firstPosition":{"lineNumber":1,"column":1},"firstPositionDeltaTop":20},"contributionsState":{"editor.contrib.folding":{},"editor.contrib.wordHighlighter":false}}}],["file:///Users/dummy/Desktop/vscode2/.gitattributes",{"0":{"cursorState":[{"inSelectionMode":false,"selectionStart":{"lineNumber":9,"column":12},"position":{"lineNumber":9,"column":12}}],"viewState":{"scrollLeft":0,"firstPosition":{"lineNumber":1,"column":1},"firstPositionDeltaTop":20},"contributionsState":{"editor.contrib.folding":{},"editor.contrib.wordHighlighter":false}}}],["file:///Users/dummy/Desktop/vscode2/src/vs/workbench/contrib/search/browser/openAnythingHandler.ts",{"0":{"cursorState":[{"inSelectionMode":false,"selectionStart":{"lineNumber":1,"column":1},"position":{"lineNumber":1,"column":1}}],"viewState":{"scrollLeft":0,"firstPosition":{"lineNumber":1,"column":1},"firstPositionDeltaTop":0},"contributionsState":{"editor.contrib.folding":{},"editor.contrib.wordHighlighter":false}}}]]}');

		const items3 = new Map<string, string>();
		items3.set('nps/iscandidate', 'false');
		items3.set('telemetry.instanceid', 'd52bfcd4-4be6-476b-a38f-d44c717c41d6');
		items3.set('workbench.activity.pinnedviewlets', '[{"id":"workbench.view.explorer","pinned":true,"order":0,"visible":true},{"id":"workbench.view.search","pinned":true,"order":1,"visible":true},{"id":"workbench.view.scm","pinned":true,"order":2,"visible":true},{"id":"workbench.view.debug","pinned":true,"order":3,"visible":true},{"id":"workbench.view.extensions","pinned":true,"order":4,"visible":true},{"id":"workbench.view.extension.gitlens","pinned":true,"order":7,"visible":true},{"id":"workbench.view.extension.test","pinned":false,"visible":false}]');
		items3.set('workbench.panel.height', '419');
		items3.set('very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.', 'is long');

		let storedItems = await storage.getItems();
		strictEqual(storedItems.size, 0);

		await Promise.all([
			await storage.updateItems({ insert: items1 }),
			await storage.updateItems({ insert: items2 }),
			await storage.updateItems({ insert: items3 })
		]);

		strictEqual(await storage.checkIntegrity(true), 'ok');
		strictEqual(await storage.checkIntegrity(false), 'ok');

		storedItems = await storage.getItems();
		strictEqual(storedItems.size, items1.size + items2.size + items3.size);

		const items1Keys: string[] = [];
		items1.forEach((value, key) => {
			items1Keys.push(key);
			strictEqual(storedItems.get(key), value);
		});

		const items2Keys: string[] = [];
		items2.forEach((value, key) => {
			items2Keys.push(key);
			strictEqual(storedItems.get(key), value);
		});

		const items3Keys: string[] = [];
		items3.forEach((value, key) => {
			items3Keys.push(key);
			strictEqual(storedItems.get(key), value);
		});

		await Promise.all([
			await storage.updateItems({ delete: toSet(items1Keys) }),
			await storage.updateItems({ delete: toSet(items2Keys) }),
			await storage.updateItems({ delete: toSet(items3Keys) })
		]);

		storedItems = await storage.getItems();
		strictEqual(storedItems.size, 0);

		await Promise.all([
			await storage.updateItems({ insert: items1 }),
			await storage.getItems(),
			await storage.updateItems({ insert: items2 }),
			await storage.getItems(),
			await storage.updateItems({ insert: items3 }),
			await storage.getItems(),
		]);

		storedItems = await storage.getItems();
		strictEqual(storedItems.size, items1.size + items2.size + items3.size);

		await storage.close();

		storage = new SQLiteStorageDatabase(join(testdir, 'storage.db'));

		storedItems = await storage.getItems();
		strictEqual(storedItems.size, items1.size + items2.size + items3.size);

		await storage.close();
	});

	test('very large item value', async function () {
		const storage = new SQLiteStorageDatabase(join(testdir, 'storage.db'));

		let randomData = createLargeRandomData(); // 3.6MB

		await storage.updateItems({ insert: randomData.items });

		let storedItems = await storage.getItems();
		strictEqual(randomData.items.get('colorthemedata'), storedItems.get('colorthemedata'));
		strictEqual(randomData.items.get('commandpalette.mru.cache'), storedItems.get('commandpalette.mru.cache'));
		strictEqual(randomData.items.get('super.large.string'), storedItems.get('super.large.string'));

		randomData = createLargeRandomData();

		await storage.updateItems({ insert: randomData.items });

		storedItems = await storage.getItems();
		strictEqual(randomData.items.get('colorthemedata'), storedItems.get('colorthemedata'));
		strictEqual(randomData.items.get('commandpalette.mru.cache'), storedItems.get('commandpalette.mru.cache'));
		strictEqual(randomData.items.get('super.large.string'), storedItems.get('super.large.string'));

		const toDelete = new Set<string>();
		toDelete.add('super.large.string');
		await storage.updateItems({ delete: toDelete });

		storedItems = await storage.getItems();
		strictEqual(randomData.items.get('colorthemedata'), storedItems.get('colorthemedata'));
		strictEqual(randomData.items.get('commandpalette.mru.cache'), storedItems.get('commandpalette.mru.cache'));
		ok(!storedItems.get('super.large.string'));

		await storage.close();
	});

	test('multiple concurrent writes execute in sequence', async () => {
		return runWithFakedTimers({}, async () => {
			class TestStorage extends Storage {
				getStorage(): IStorageDatabase {
					return this.database;
				}
			}

			const storage = new TestStorage(new SQLiteStorageDatabase(join(testdir, 'storage.db')));

			await storage.init();

			storage.set('foo', 'bar');
			storage.set('some/foo/path', 'some/bar/path');

			await timeout(2);

			storage.set('foo1', 'bar');
			storage.set('some/foo1/path', 'some/bar/path');

			await timeout(2);

			storage.set('foo2', 'bar');
			storage.set('some/foo2/path', 'some/bar/path');

			await timeout(2);

			storage.delete('foo1');
			storage.delete('some/foo1/path');

			await timeout(2);

			storage.delete('foo4');
			storage.delete('some/foo4/path');

			await timeout(5);

			storage.set('foo3', 'bar');
			await storage.set('some/foo3/path', 'some/bar/path');

			const items = await storage.getStorage().getItems();
			strictEqual(items.get('foo'), 'bar');
			strictEqual(items.get('some/foo/path'), 'some/bar/path');
			strictEqual(items.has('foo1'), false);
			strictEqual(items.has('some/foo1/path'), false);
			strictEqual(items.get('foo2'), 'bar');
			strictEqual(items.get('some/foo2/path'), 'some/bar/path');
			strictEqual(items.get('foo3'), 'bar');
			strictEqual(items.get('some/foo3/path'), 'some/bar/path');

			await storage.close();
		});
	});

	test('lots of INSERT & DELETE (below inline max)', async () => {
		const storage = new SQLiteStorageDatabase(join(testdir, 'storage.db'));

		const { items, keys } = createManyRandomData(200);

		await storage.updateItems({ insert: items });

		let storedItems = await storage.getItems();
		strictEqual(storedItems.size, items.size);

		await storage.updateItems({ delete: keys });

		storedItems = await storage.getItems();
		strictEqual(storedItems.size, 0);

		await storage.close();
	});

	test('lots of INSERT & DELETE (above inline max)', async () => {
		const storage = new SQLiteStorageDatabase(join(testdir, 'storage.db'));

		const { items, keys } = createManyRandomData();

		await storage.updateItems({ insert: items });

		let storedItems = await storage.getItems();
		strictEqual(storedItems.size, items.size);

		await storage.updateItems({ delete: keys });

		storedItems = await storage.getItems();
		strictEqual(storedItems.size, 0);

		await storage.close();
	});

	test('invalid path does not hang', async () => {
		const storage = new SQLiteStorageDatabase(join(testdir, 'nonexist', 'storage.db'));

		let error;
		try {
			await storage.getItems();
			await storage.close();
		} catch (e) {
			error = e;
		}

		ok(error);
	});

	test('optimize', async () => {
		const dbPath = join(testdir, 'storage.db');
		let storage = new SQLiteStorageDatabase(dbPath);

		const { items, keys } = createManyRandomData(400, true);

		await storage.updateItems({ insert: items });

		let storedItems = await storage.getItems();
		strictEqual(storedItems.size, items.size);

		await storage.optimize();
		await storage.close();

		const sizeBeforeDeleteAndOptimize = (await fs.promises.stat(dbPath)).size;

		storage = new SQLiteStorageDatabase(dbPath);

		storedItems = await storage.getItems();
		strictEqual(storedItems.size, items.size);

		await storage.updateItems({ delete: keys });

		storedItems = await storage.getItems();
		strictEqual(storedItems.size, 0);

		await storage.optimize();
		await storage.close();

		storage = new SQLiteStorageDatabase(dbPath);

		storedItems = await storage.getItems();
		strictEqual(storedItems.size, 0);

		await storage.close();

		const sizeAfterDeleteAndOptimize = (await fs.promises.stat(dbPath)).size;

		strictEqual(sizeAfterDeleteAndOptimize < sizeBeforeDeleteAndOptimize, true);
	});

	function createManyRandomData(length = 400, includeVeryLarge = false) {
		const items = new Map<string, string>();
		const keys = new Set<string>();

		for (let i = 0; i < length; i++) {
			const uuid = generateUuid();
			const key = `key: ${uuid}`;

			items.set(key, `value: ${uuid}`);
			keys.add(key);
		}

		if (includeVeryLarge) {
			const largeData = createLargeRandomData();
			for (const [key, value] of largeData.items) {
				items.set(key, value);
				keys.add(key);
			}
		}

		return { items, keys };
	}

	function createLargeRandomData() {
		const items = new Map<string, string>();
		items.set('colorthemedata', '{"id":"vs vscode-theme-defaults-themes-light_plus-json","label":"Light+ (default light)","settingsId":"Default Light+","selector":"vs.vscode-theme-defaults-themes-light_plus-json","themeTokenColors":[{"settings":{"foreground":"#000000ff","background":"#ffffffff"}},{"scope":["meta.embedded","source.groovy.embedded"],"settings":{"foreground":"#000000ff"}},{"scope":"emphasis","settings":{"fontStyle":"italic"}},{"scope":"strong","settings":{"fontStyle":"bold"}},{"scope":"meta.diff.header","settings":{"foreground":"#000080"}},{"scope":"comment","settings":{"foreground":"#008000"}},{"scope":"constant.language","settings":{"foreground":"#0000ff"}},{"scope":["constant.numeric"],"settings":{"foreground":"#098658"}},{"scope":"constant.regexp","settings":{"foreground":"#811f3f"}},{"name":"css tags in selectors, xml tags","scope":"entity.name.tag","settings":{"foreground":"#800000"}},{"scope":"entity.name.selector","settings":{"foreground":"#800000"}},{"scope":"entity.other.attribute-name","settings":{"foreground":"#ff0000"}},{"scope":["entity.other.attribute-name.class.css","entity.other.attribute-name.class.mixin.css","entity.other.attribute-name.id.css","entity.other.attribute-name.parent-selector.css","entity.other.attribute-name.pseudo-class.css","entity.other.attribute-name.pseudo-element.css","source.css.less entity.other.attribute-name.id","entity.other.attribute-name.attribute.scss","entity.other.attribute-name.scss"],"settings":{"foreground":"#800000"}},{"scope":"invalid","settings":{"foreground":"#cd3131"}},{"scope":"markup.underline","settings":{"fontStyle":"underline"}},{"scope":"markup.bold","settings":{"fontStyle":"bold","foreground":"#000080"}},{"scope":"markup.heading","settings":{"fontStyle":"bold","foreground":"#800000"}},{"scope":"markup.italic","settings":{"fontStyle":"italic"}},{"scope":"markup.inserted","settings":{"foreground":"#098658"}},{"scope":"markup.deleted","settings":{"foreground":"#a31515"}},{"scope":"markup.changed","settings":{"foreground":"#0451a5"}},{"scope":["punctuation.definition.quote.begin.markdown","punctuation.definition.list.begin.markdown"],"settings":{"foreground":"#0451a5"}},{"scope":"markup.inline.raw","settings":{"foreground":"#800000"}},{"name":"brackets of XML/HTML tags","scope":"punctuation.definition.tag","settings":{"foreground":"#800000"}},{"scope":"meta.preprocessor","settings":{"foreground":"#0000ff"}},{"scope":"meta.preprocessor.string","settings":{"foreground":"#a31515"}},{"scope":"meta.preprocessor.numeric","settings":{"foreground":"#098658"}},{"scope":"meta.structure.dictionary.key.python","settings":{"foreground":"#0451a5"}},{"scope":"storage","settings":{"foreground":"#0000ff"}},{"scope":"storage.type","settings":{"foreground":"#0000ff"}},{"scope":"storage.modifier","settings":{"foreground":"#0000ff"}},{"scope":"string","settings":{"foreground":"#a31515"}},{"scope":["string.comment.buffered.block.pug","string.quoted.pug","string.interpolated.pug","string.unquoted.plain.in.yaml","string.unquoted.plain.out.yaml","string.unquoted.block.yaml","string.quoted.single.yaml","string.quoted.double.xml","string.quoted.single.xml","string.unquoted.cdata.xml","string.quoted.double.html","string.quoted.single.html","string.unquoted.html","string.quoted.single.handlebars","string.quoted.double.handlebars"],"settings":{"foreground":"#0000ff"}},{"scope":"string.regexp","settings":{"foreground":"#811f3f"}},{"name":"String interpolation","scope":["punctuation.definition.template-expression.begin","punctuation.definition.template-expression.end","punctuation.section.embedded"],"settings":{"foreground":"#0000ff"}},{"name":"Reset JavaScript string interpolation expression","scope":["meta.template.expression"],"settings":{"foreground":"#000000"}},{"scope":["support.constant.property-value","support.constant.font-name","support.constant.media-type","support.constant.media","constant.other.color.rgb-value","constant.other.rgb-value","support.constant.color"],"settings":{"foreground":"#0451a5"}},{"scope":["support.type.vendored.property-name","support.type.property-name","variable.css","variable.scss","variable.other.less","source.coffee.embedded"],"settings":{"foreground":"#ff0000"}},{"scope":["support.type.property-name.json"],"settings":{"foreground":"#0451a5"}},{"scope":"keyword","settings":{"foreground":"#0000ff"}},{"scope":"keyword.control","settings":{"foreground":"#0000ff"}},{"scope":"keyword.operator","settings":{"foreground":"#000000"}},{"scope":["keyword.operator.new","keyword.operator.expression","keyword.operator.cast","keyword.operator.sizeof","keyword.operator.instanceof","keyword.operator.logical.python"],"settings":{"foreground":"#0000ff"}},{"scope":"keyword.other.unit","settings":{"foreground":"#098658"}},{"scope":["punctuation.section.embedded.begin.php","punctuation.section.embedded.end.php"],"settings":{"foreground":"#800000"}},{"scope":"support.function.git-rebase","settings":{"foreground":"#0451a5"}},{"scope":"constant.sha.git-rebase","settings":{"foreground":"#098658"}},{"name":"coloring of the Java import and package identifiers","scope":["storage.modifier.import.java","variable.language.wildcard.java","storage.modifier.package.java"],"settings":{"foreground":"#000000"}},{"name":"this.self","scope":"variable.language","settings":{"foreground":"#0000ff"}},{"name":"Function declarations","scope":["entity.name.function","support.function","support.constant.handlebars"],"settings":{"foreground":"#795E26"}},{"name":"Types declaration and references","scope":["meta.return-type","support.class","support.type","entity.name.type","entity.name.class","storage.type.numeric.go","storage.type.byte.go","storage.type.boolean.go","storage.type.string.go","storage.type.uintptr.go","storage.type.error.go","storage.type.rune.go","storage.type.cs","storage.type.generic.cs","storage.type.modifier.cs","storage.type.variable.cs","storage.type.annotation.java","storage.type.generic.java","storage.type.java","storage.type.object.array.java","storage.type.primitive.array.java","storage.type.primitive.java","storage.type.token.java","storage.type.groovy","storage.type.annotation.groovy","storage.type.parameters.groovy","storage.type.generic.groovy","storage.type.object.array.groovy","storage.type.primitive.array.groovy","storage.type.primitive.groovy"],"settings":{"foreground":"#267f99"}},{"name":"Types declaration and references, TS grammar specific","scope":["meta.type.cast.expr","meta.type.new.expr","support.constant.math","support.constant.dom","support.constant.json","entity.other.inherited-class"],"settings":{"foreground":"#267f99"}},{"name":"Control flow keywords","scope":"keyword.control","settings":{"foreground":"#AF00DB"}},{"name":"Variable and parameter name","scope":["variable","meta.definition.variable.name","support.variable","entity.name.variable"],"settings":{"foreground":"#001080"}},{"name":"Object keys, TS grammar specific","scope":["meta.object-literal.key"],"settings":{"foreground":"#001080"}},{"name":"CSS property value","scope":["support.constant.property-value","support.constant.font-name","support.constant.media-type","support.constant.media","constant.other.color.rgb-value","constant.other.rgb-value","support.constant.color"],"settings":{"foreground":"#0451a5"}},{"name":"Regular expression groups","scope":["punctuation.definition.group.regexp","punctuation.definition.group.assertion.regexp","punctuation.definition.character-class.regexp","punctuation.character.set.begin.regexp","punctuation.character.set.end.regexp","keyword.operator.negation.regexp","support.other.parenthesis.regexp"],"settings":{"foreground":"#d16969"}},{"scope":["constant.character.character-class.regexp","constant.other.character-class.set.regexp","constant.other.character-class.regexp","constant.character.set.regexp"],"settings":{"foreground":"#811f3f"}},{"scope":"keyword.operator.quantifier.regexp","settings":{"foreground":"#000000"}},{"scope":["keyword.operator.or.regexp","keyword.control.anchor.regexp"],"settings":{"foreground":"#ff0000"}},{"scope":"constant.character","settings":{"foreground":"#0000ff"}},{"scope":"constant.character.escape","settings":{"foreground":"#ff0000"}},{"scope":"token.info-token","settings":{"foreground":"#316bcd"}},{"scope":"token.warn-token","settings":{"foreground":"#cd9731"}},{"scope":"token.error-token","settings":{"foreground":"#cd3131"}},{"scope":"token.debug-token","settings":{"foreground":"#800080"}}],"extensionData":{"extensionId":"vscode.theme-defaults","extensionPublisher":"vscode","extensionName":"theme-defaults","extensionIsBuiltin":true},"colorMap":{"editor.background":"#ffffff","editor.foreground":"#000000","editor.inactiveSelectionBackground":"#e5ebf1","editorIndentGuide.background":"#d3d3d3","editorIndentGuide.activeBackground":"#939393","editor.selectionHighlightBackground":"#add6ff4d","editorSuggestWidget.background":"#f3f3f3","activityBarBadge.background":"#007acc","sideBarTitle.foreground":"#6f6f6f","list.hoverBackground":"#e8e8e8","input.placeholderForeground":"#767676","settings.textInputBorder":"#cecece","settings.numberInputBorder":"#cecece"}}');
		items.set('commandpalette.mru.cache', '{"usesLRU":true,"entries":[{"key":"revealFileInOS","value":3},{"key":"extension.openInGitHub","value":4},{"key":"workbench.extensions.action.openExtensionsFolder","value":11},{"key":"workbench.action.showRuntimeExtensions","value":14},{"key":"workbench.action.toggleTabsVisibility","value":15},{"key":"extension.liveServerPreview.open","value":16},{"key":"workbench.action.openIssueReporter","value":18},{"key":"workbench.action.openProcessExplorer","value":19},{"key":"workbench.action.toggleSharedProcess","value":20},{"key":"workbench.action.configureLocale","value":21},{"key":"workbench.action.appPerf","value":22},{"key":"workbench.action.reportPerformanceIssueUsingReporter","value":23},{"key":"workbench.action.openGlobalKeybindings","value":25},{"key":"workbench.action.output.toggleOutput","value":27},{"key":"extension.sayHello","value":29}]}');

		const uuid = generateUuid();
		const value: string[] = [];
		for (let i = 0; i < 100000; i++) {
			value.push(uuid);
		}

		items.set('super.large.string', value.join()); // 3.6MB

		return { items, uuid, value };
	}
});


