<commit_before>import { strict as assert } from "assert";
import * as path from "path";
import * as vs from "vscode";
import { Outline as lspOutline } from "../../../shared/analysis/lsp/custom_protocol";
import { Outline as asOutline } from "../../../shared/analysis_server_types";
import { isWin } from "../../../shared/constants";
import { DebuggerType } from "../../../shared/enums";
import { getPackageTestCapabilities } from "../../../shared/test/version";
import { fsPath } from "../../../shared/utils/fs";
import { DasTestOutlineInfo, TestOutlineVisitor } from "../../../shared/utils/outline_das";
import { LspTestOutlineInfo, LspTestOutlineVisitor } from "../../../shared/utils/outline_lsp";
import { waitFor } from "../../../shared/utils/promises";
import * as testUtils from "../../../shared/utils/test";
import { DartDebugClient } from "../../dart_debug_client";
import { createDebugClient, startDebugger, waitAllThrowIfTerminates } from "../../debug_helpers";
import { activate, captureDebugSessionCustomEvents, checkTreeNodeResults, clearTestTree, customScriptExt, delay, ensureArrayContainsArray, ensureHasRunWithArgsStarting, extApi, fakeCancellationToken, getCodeLens, getExpectedResults, getPackages, getResolvedDebugConfiguration, helloWorldExampleSubFolderProjectTestFile, helloWorldFolder, helloWorldProjectTestFile, helloWorldTestBrokenFile, helloWorldTestDupeNameFile, helloWorldTestEnvironmentFile, helloWorldTestMainFile, helloWorldTestShortFile, helloWorldTestTreeFile, logger, makeTestTextTree, openFile as openFileBasic, positionOf, prepareHasRunFile, setConfigForTest, waitForResult } from "../../helpers";

describe("dart test debugger", () => {
	// We have tests that require external packages.
	before("get packages", () => getPackages());
	beforeEach("activate", () => activate(null));

	beforeEach(function () {
		if (isWin && !extApi.dartCapabilities.hasDdsTimingFix)
			this.skip();
	});

	let dc: DartDebugClient;
	beforeEach("create debug client", function () {
		if (process.env.DART_CODE_FORCE_SDK_DAP === "true" && !extApi.dartCapabilities.supportsSdkDap)
			this.skip();

		dc = createDebugClient(DebuggerType.DartTest);
	});

	beforeEach("clear test tree", () => clearTestTree());

	/// Wrap openFile to force test discovery to re-run since we clear the test
	/// tree state between tests, but some tests rely on the Outline-populated test
	/// nodes to verify trees.
	async function openFile(file: vs.Uri): Promise<vs.TextEditor> {
		const editor = await openFileBasic(file);
		extApi.testDiscoverer.forceUpdate(file);
		return editor;
	}

	describe("resolves the correct debug config", () => {
		it("passing launch.json's toolArgs to the VM", async () => {
			const resolvedConfig = await getResolvedDebugConfiguration({
				program: fsPath(helloWorldTestMainFile),
				toolArgs: ["--fake-flag"],
			})!;

			assert.ok(resolvedConfig);
			assert.equal(resolvedConfig.program, fsPath(helloWorldTestMainFile));
			assert.equal(resolvedConfig.cwd, fsPath(helloWorldFolder));
			ensureArrayContainsArray(resolvedConfig.toolArgs!, ["--fake-flag"]);
		});

		it("when testAdditionalArgs is set", async () => {
			await setConfigForTest("dart", "testAdditionalArgs", ["--my-test-flag"]);
			const resolvedConfig = await getResolvedDebugConfiguration({
				program: fsPath(helloWorldTestMainFile),
			})!;

			ensureArrayContainsArray(resolvedConfig.toolArgs!, ["--my-test-flag"]);
		});

		it("when suppressTestTimeouts is set", async () => {
			await setConfigForTest("dart", "suppressTestTimeouts", "always");
			const resolvedConfig = await getResolvedDebugConfiguration({
				program: fsPath(helloWorldTestMainFile),
			})!;

			const testCapabilities = await getPackageTestCapabilities(extApi.logger, extApi.workspaceContext.sdks, resolvedConfig.cwd!);
			if (testCapabilities.supportsIgnoreTimeouts)
				ensureArrayContainsArray(resolvedConfig.toolArgs!, ["--ignore-timeouts"]);
			else
				ensureArrayContainsArray(resolvedConfig.toolArgs!, ["--timeout"]);
		});
	});

	it("runs a Dart test script to completion", async () => {
		await openFile(helloWorldTestMainFile);
		const config = await startDebugger(dc, helloWorldTestMainFile);
		await waitAllThrowIfTerminates(dc,
			dc.configurationSequence(),
			dc.waitForEvent("terminated"),
			dc.launch(config),
		);
	});

	it("can run tests from codelens", async function () {
		const editor = await openFile(helloWorldTestMainFile);
		await waitForResult(() => !!extApi.fileTracker.getOutlineFor(helloWorldTestMainFile));

		const fileCodeLens = await getCodeLens(editor.document);
		const testPos = positionOf(`test^(".split() splits`);

		const codeLensForTest = fileCodeLens.filter((cl) => cl.range.start.line === testPos.line);
		assert.equal(codeLensForTest.length, 2);

		if (!codeLensForTest[0].command) {
			// If there's no command, skip the test. This happens very infrequently and appears to be a VS Code
			// race condition. Rather than failing our test runs, skip.
			// TODO: Remove this if https://github.com/microsoft/vscode/issues/79805 gets a reliable fix.
			this.skip();
			return;
		}

		const runAction = codeLensForTest.find((cl) => cl.command!.title === "Run")!;
		assert.equal(runAction.command!.command, "_dart.startWithoutDebuggingTestFromOutline");
		assert.equal(runAction.command!.arguments![0].fullName, "String .split() splits the string on the delimiter");
		assert.equal(runAction.command!.arguments![0].isGroup, false);

		const customEvents = await captureDebugSessionCustomEvents(async () => {
			const didStart = await vs.commands.executeCommand(runAction.command!.command, ...(runAction.command!.arguments ?? [])); // eslint-disable-line @typescript-eslint/no-unsafe-argument
			assert.ok(didStart);
		});
		// Ensure we got at least a "testDone" notification so we know the test run started correctly.
		const testDoneNotification = customEvents.find((e) => e.event === "dart.testNotification" && e.body.type === "testDone");
		assert.ok(testDoneNotification, JSON.stringify(customEvents.map((e) => e.body), undefined, 4));
	});

	it("can run using a custom tool", async () => {
		const root = fsPath(helloWorldFolder);
		const hasRunFile = prepareHasRunFile(root, "dart_test");

		const config = await startDebugger(dc, helloWorldTestMainFile, {
			customTool: path.join(root, `scripts/custom_test.${customScriptExt}`),
			// Replace "run --no-spawn-devtools test:test"
			customToolReplacesArgs: 3,
			enableAsserts: false,
			noDebug: true,
		});
		await waitAllThrowIfTerminates(dc,
			dc.configurationSequence(),
			dc.waitForEvent("terminated"),
			dc.launch(config),
		);

		ensureHasRunWithArgsStarting(root, hasRunFile, `-r json`);
	});

	it("can replace all args using custom tool", async () => {
		const root = fsPath(helloWorldFolder);
		const hasRunFile = prepareHasRunFile(root, "dart_test");

		const config = await startDebugger(dc, helloWorldTestMainFile, {
			customTool: path.join(root, `scripts/custom_test.${customScriptExt}`),
			customToolReplacesArgs: 999999,
			enableAsserts: false,
			noDebug: true,
			// These differ to the usual ones so we can detect they replaced them.
			toolArgs: ["-j2", "-r", "json"],
		});
		await waitAllThrowIfTerminates(dc,
			dc.configurationSequence(),
			dc.waitForEvent("terminated"),
			dc.launch(config),
		);

		ensureHasRunWithArgsStarting(root, hasRunFile, `-j2 -r json`);
	});

	it("receives the expected events from a Dart test script", async () => {
		await openFile(helloWorldTestMainFile);
		const config = await startDebugger(dc, helloWorldTestMainFile);
		await waitAllThrowIfTerminates(dc,
			dc.configurationSequence(),
			dc.assertOutput("stdout", `✓ String .split() splits the string on the delimiter`),
			dc.assertPassingTest("String .split() splits the string on the delimiter"),
			dc.waitForEvent("terminated"),
			dc.launch(config),
		);
	});

	it("stops at a breakpoint", async () => {
		await openFile(helloWorldTestMainFile);
		const config = await startDebugger(dc, helloWorldTestMainFile);
		await dc.hitBreakpoint(config, {
			line: positionOf("^// BREAKPOINT1").line + 1, // positionOf is 0-based, but seems to want 1-based
			path: fsPath(helloWorldTestMainFile),
		});
	});

	it("stops on exception", async () => {
		await openFile(helloWorldTestBrokenFile);
		const config = await startDebugger(dc, helloWorldTestBrokenFile);
		await waitAllThrowIfTerminates(dc,
			dc.configurationSequence(),
			dc.assertStoppedLocation("exception", {}),<commit_msg>Fix test when running using SDK DAPs<commit_after>import { strict as assert } from "assert";
import * as path from "path";
import * as vs from "vscode";
import { Outline as lspOutline } from "../../../shared/analysis/lsp/custom_protocol";
import { Outline as asOutline } from "../../../shared/analysis_server_types";
import { isWin } from "../../../shared/constants";
import { DebuggerType } from "../../../shared/enums";
import { getPackageTestCapabilities } from "../../../shared/test/version";
import { fsPath } from "../../../shared/utils/fs";
import { DasTestOutlineInfo, TestOutlineVisitor } from "../../../shared/utils/outline_das";
import { LspTestOutlineInfo, LspTestOutlineVisitor } from "../../../shared/utils/outline_lsp";
import { waitFor } from "../../../shared/utils/promises";
import * as testUtils from "../../../shared/utils/test";
import { DartDebugClient } from "../../dart_debug_client";
import { createDebugClient, startDebugger, waitAllThrowIfTerminates } from "../../debug_helpers";
import { activate, captureDebugSessionCustomEvents, checkTreeNodeResults, clearTestTree, customScriptExt, delay, ensureArrayContainsArray, ensureHasRunWithArgsStarting, extApi, fakeCancellationToken, getCodeLens, getExpectedResults, getPackages, getResolvedDebugConfiguration, helloWorldExampleSubFolderProjectTestFile, helloWorldFolder, helloWorldProjectTestFile, helloWorldTestBrokenFile, helloWorldTestDupeNameFile, helloWorldTestEnvironmentFile, helloWorldTestMainFile, helloWorldTestShortFile, helloWorldTestTreeFile, logger, makeTestTextTree, openFile as openFileBasic, positionOf, prepareHasRunFile, setConfigForTest, waitForResult } from "../../helpers";

describe("dart test debugger", () => {
	// We have tests that require external packages.
	before("get packages", () => getPackages());
	beforeEach("activate", () => activate(null));

	beforeEach(function () {
		if (isWin && !extApi.dartCapabilities.hasDdsTimingFix)
			this.skip();
	});

	let dc: DartDebugClient;
	beforeEach("create debug client", function () {
		if (process.env.DART_CODE_FORCE_SDK_DAP === "true" && !extApi.dartCapabilities.supportsSdkDap)
			this.skip();

		dc = createDebugClient(DebuggerType.DartTest);
	});

	beforeEach("clear test tree", () => clearTestTree());

	/// Wrap openFile to force test discovery to re-run since we clear the test
	/// tree state between tests, but some tests rely on the Outline-populated test
	/// nodes to verify trees.
	async function openFile(file: vs.Uri): Promise<vs.TextEditor> {
		const editor = await openFileBasic(file);
		extApi.testDiscoverer.forceUpdate(file);
		return editor;
	}

	describe("resolves the correct debug config", () => {
		it("passing launch.json's toolArgs to the VM", async () => {
			const resolvedConfig = await getResolvedDebugConfiguration({
				program: fsPath(helloWorldTestMainFile),
				toolArgs: ["--fake-flag"],
			})!;

			assert.ok(resolvedConfig);
			assert.equal(resolvedConfig.program, fsPath(helloWorldTestMainFile));
			assert.equal(resolvedConfig.cwd, fsPath(helloWorldFolder));
			ensureArrayContainsArray(resolvedConfig.toolArgs!, ["--fake-flag"]);
		});

		it("when testAdditionalArgs is set", async () => {
			await setConfigForTest("dart", "testAdditionalArgs", ["--my-test-flag"]);
			const resolvedConfig = await getResolvedDebugConfiguration({
				program: fsPath(helloWorldTestMainFile),
			})!;

			ensureArrayContainsArray(resolvedConfig.toolArgs!, ["--my-test-flag"]);
		});

		it("when suppressTestTimeouts is set", async () => {
			await setConfigForTest("dart", "suppressTestTimeouts", "always");
			const resolvedConfig = await getResolvedDebugConfiguration({
				program: fsPath(helloWorldTestMainFile),
			})!;

			const testCapabilities = await getPackageTestCapabilities(extApi.logger, extApi.workspaceContext.sdks, resolvedConfig.cwd!);
			if (testCapabilities.supportsIgnoreTimeouts)
				ensureArrayContainsArray(resolvedConfig.toolArgs!, ["--ignore-timeouts"]);
			else
				ensureArrayContainsArray(resolvedConfig.toolArgs!, ["--timeout"]);
		});
	});

	it("runs a Dart test script to completion", async () => {
		await openFile(helloWorldTestMainFile);
		const config = await startDebugger(dc, helloWorldTestMainFile);
		await waitAllThrowIfTerminates(dc,
			dc.configurationSequence(),
			dc.waitForEvent("terminated"),
			dc.launch(config),
		);
	});

	it("can run tests from codelens", async function () {
		const editor = await openFile(helloWorldTestMainFile);
		await waitForResult(() => !!extApi.fileTracker.getOutlineFor(helloWorldTestMainFile));

		const fileCodeLens = await getCodeLens(editor.document);
		const testPos = positionOf(`test^(".split() splits`);

		const codeLensForTest = fileCodeLens.filter((cl) => cl.range.start.line === testPos.line);
		assert.equal(codeLensForTest.length, 2);

		if (!codeLensForTest[0].command) {
			// If there's no command, skip the test. This happens very infrequently and appears to be a VS Code
			// race condition. Rather than failing our test runs, skip.
			// TODO: Remove this if https://github.com/microsoft/vscode/issues/79805 gets a reliable fix.
			this.skip();
			return;
		}

		const runAction = codeLensForTest.find((cl) => cl.command!.title === "Run")!;
		assert.equal(runAction.command!.command, "_dart.startWithoutDebuggingTestFromOutline");
		assert.equal(runAction.command!.arguments![0].fullName, "String .split() splits the string on the delimiter");
		assert.equal(runAction.command!.arguments![0].isGroup, false);

		const customEvents = await captureDebugSessionCustomEvents(async () => {
			const didStart = await vs.commands.executeCommand(runAction.command!.command, ...(runAction.command!.arguments ?? [])); // eslint-disable-line @typescript-eslint/no-unsafe-argument
			assert.ok(didStart);
		});
		// Ensure we got at least a "testDone" notification so we know the test run started correctly.
		const testDoneNotification = customEvents.find((e) => e.event === "dart.testNotification" && e.body.type === "testDone");
		assert.ok(testDoneNotification, JSON.stringify(customEvents.map((e) => e.body), undefined, 4));
	});

	it("can run using a custom tool", async () => {
		const root = fsPath(helloWorldFolder);
		const hasRunFile = prepareHasRunFile(root, "dart_test");

		const config = await startDebugger(dc, helloWorldTestMainFile, {
			customTool: path.join(root, `scripts/custom_test.${customScriptExt}`),
			// Replace "run --no-spawn-devtools test:test"
			customToolReplacesArgs: 3,
			enableAsserts: false,
			noDebug: true,
		});
		await waitAllThrowIfTerminates(dc,
			dc.configurationSequence(),
			dc.waitForEvent("terminated"),
			dc.launch(config),
		);

		ensureHasRunWithArgsStarting(root, hasRunFile, `-r json`);
	});

	it("can replace all args using custom tool", async () => {
		const root = fsPath(helloWorldFolder);
		const hasRunFile = prepareHasRunFile(root, "dart_test");

		const config = await startDebugger(dc, helloWorldTestMainFile, {
			customTool: path.join(root, `scripts/custom_test.${customScriptExt}`),
			customToolReplacesArgs: 999999,
			enableAsserts: false,
			noDebug: true,
			// These differ to the usual ones so we can detect they replaced them.
			toolArgs: ["-j2", "-r", "json"],
		});
		await waitAllThrowIfTerminates(dc,
			dc.configurationSequence(),
			dc.waitForEvent("terminated"),
			dc.launch(config),
		);

		ensureHasRunWithArgsStarting(root, hasRunFile, `-j2 -r json`);
	});

	it("receives the expected events from a Dart test script", async () => {
		await openFile(helloWorldTestMainFile);
		const config = await startDebugger(dc, helloWorldTestMainFile);
		await waitAllThrowIfTerminates(dc,
			dc.configurationSequence(),
			dc.assertOutputContains(dc.isDartDap ? "console" : "stdout", `✓ String .split() splits the string on the delimiter`),
			dc.assertPassingTest("String .split() splits the string on the delimiter"),
			dc.waitForEvent("terminated"),
			dc.launch(config),
		);
	});

	it("stops at a breakpoint", async () => {
		await openFile(helloWorldTestMainFile);
		const config = await startDebugger(dc, helloWorldTestMainFile);
		await dc.hitBreakpoint(config, {
			line: positionOf("^// BREAKPOINT1").line + 1, // positionOf is 0-based, but seems to want 1-based
			path: fsPath(helloWorldTestMainFile),
		});
	});

	it("stops on exception", async () => {
		await openFile(helloWorldTestBrokenFile);
		const config = await startDebugger(dc, helloWorldTestBrokenFile);
		await waitAllThrowIfTerminates(dc,
			dc.configurationSequence(),
			dc.assertStoppedLocation("exception", {}),
{'fraction_non_alphanumeric': 0.13982602562595509, 'fraction_numerical': 0.0027036558128599977, 'mean_word_length': 4.991197183098592, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 45, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2129742', 'n_tokens_mistral': 5534, 'n_tokens_neox': 5068, 'n_words': 1180}