Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test Explorer in VS Code Extension and @Test() Attribute #2059

Open
wants to merge 42 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b79c31e
initial work on testing
sezna Dec 10, 2024
1c09783
Merge branch 'main' of github.com:microsoft/qsharp into alex/testHarness
sezna Dec 10, 2024
54eb209
progress on test explorer
sezna Dec 10, 2024
e6a0941
test collection works
sezna Dec 10, 2024
0495a6d
tests run
sezna Dec 10, 2024
eae1b4d
add todos
sezna Dec 10, 2024
759036f
switching to namespaces included with callable names
sezna Dec 10, 2024
cec4bf9
scoped test names
sezna Dec 10, 2024
ad7d3e2
deduplicate parent items
sezna Dec 12, 2024
d9f288d
make running child items work
sezna Dec 12, 2024
9ec9f41
auto-refresh test cases
sezna Dec 12, 2024
a03ed14
wip -- checkpoint
sezna Dec 12, 2024
c11476a
wip
sezna Dec 12, 2024
b247c35
Remove codelens stuff
sezna Dec 12, 2024
e75f65b
update libraries to use new testing harness
sezna Dec 12, 2024
131a341
remove bad imports
sezna Dec 12, 2024
b52ad25
document some functions
sezna Dec 12, 2024
180368e
Add comment
sezna Dec 12, 2024
e960916
Remove todos
sezna Dec 12, 2024
7d2aadc
Fix nested tests
sezna Dec 12, 2024
ac8bfd7
initial round of PR feedback
sezna Dec 13, 2024
c22d14d
move discovery of test items into compiler layer
sezna Dec 13, 2024
5a28ef1
Use a pass to detect test attribute errors and report them nicely
sezna Dec 16, 2024
16a2aed
use getActiveProgram
sezna Dec 16, 2024
214097c
remove unnecessary api in main.ts
sezna Dec 16, 2024
adbc4b2
Fmt
sezna Dec 16, 2024
5b6a1f8
fix lints
sezna Dec 16, 2024
7cee532
update tests
sezna Dec 16, 2024
be64105
filter out invalid test items
sezna Dec 16, 2024
e34b7d2
Merge branch 'main' of github.com:microsoft/qsharp into alex/testHarness
sezna Dec 18, 2024
fa1ca42
wip: start to add locations to the return type for test callables
sezna Dec 18, 2024
790c29b
get spans/ranges hooked up
sezna Dec 18, 2024
9d0190c
abstract compiler worker generation into a common singleton worker
sezna Dec 20, 2024
38e0f4b
wipz
sezna Dec 20, 2024
43f4e17
use updateDocument events for test discovery
sezna Dec 20, 2024
bcd6d34
it works, but i'd rather not have the tests collapse on auto refresh
sezna Dec 23, 2024
5421cb2
Fmt
sezna Dec 23, 2024
804fb0b
rename Vscode to VsCode
sezna Dec 23, 2024
ffab724
rename collectTestCallables to getTestCallables
sezna Dec 23, 2024
cca48b5
switch to debug event target; remove unnecessary result
sezna Dec 23, 2024
eea9581
rename test explorer to test discovery
sezna Dec 23, 2024
540de0b
fmt
sezna Dec 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/qsc_frontend/src/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@ impl With<'_> {
None
}
},
Ok(hir::Attr::Test) => Some(hir::Attr::Test),
sezna marked this conversation as resolved.
Show resolved Hide resolved
Err(()) => {
self.lowerer.errors.push(Error::UnknownAttr(
attr.name.name.to_string(),
Expand Down
4 changes: 4 additions & 0 deletions compiler/qsc_hir/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1359,6 +1359,8 @@ pub enum Attr {
/// Indicates that an intrinsic callable is a reset. This means that the operation will be marked as
/// "irreversible" in the generated QIR.
Reset,
/// Indicates that a callable is a test case.
Test,
}

impl Attr {
Expand All @@ -1376,6 +1378,7 @@ The `not` operator is also supported to negate the attribute, e.g. `not Adaptive
Attr::SimulatableIntrinsic => "Indicates that an item should be treated as an intrinsic callable for QIR code generation and any implementation should only be used during simulation.",
Attr::Measurement => "Indicates that an intrinsic callable is a measurement. This means that the operation will be marked as \"irreversible\" in the generated QIR, and output Result types will be moved to the arguments.",
Attr::Reset => "Indicates that an intrinsic callable is a reset. This means that the operation will be marked as \"irreversible\" in the generated QIR.",
Attr::Test => "Indicates that a callable is a test case.",
}
}
}
Expand All @@ -1391,6 +1394,7 @@ impl FromStr for Attr {
"SimulatableIntrinsic" => Ok(Self::SimulatableIntrinsic),
"Measurement" => Ok(Self::Measurement),
"Reset" => Ok(Self::Reset),
"Test" => Ok(Self::Test),
_ => Err(()),
}
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/qsc_lowerer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -943,7 +943,7 @@ fn lower_attrs(attrs: &[hir::Attr]) -> Vec<fir::Attr> {
hir::Attr::EntryPoint => Some(fir::Attr::EntryPoint),
hir::Attr::Measurement => Some(fir::Attr::Measurement),
hir::Attr::Reset => Some(fir::Attr::Reset),
hir::Attr::SimulatableIntrinsic | hir::Attr::Unimplemented | hir::Attr::Config => None,
hir::Attr::SimulatableIntrinsic | hir::Attr::Unimplemented | hir::Attr::Config | hir::Attr::Test => None,
})
.collect()
}
Expand Down
17 changes: 17 additions & 0 deletions compiler/qsc_parse/src/item/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2396,3 +2396,20 @@ fn top_level_nodes_error_recovery() {
]"#]],
);
}

#[test]
fn test_attribute() {
check(
parse,
"@Test() function Foo() : Unit {}",
&expect![[r#"
Item _id_ [0-32]:
Attr _id_ [0-7] (Ident _id_ [1-5] "Test"):
Expr _id_ [5-7]: Unit
Callable _id_ [8-32] (Function):
name: Ident _id_ [17-20] "Foo"
input: Pat _id_ [20-22]: Unit
output: Type _id_ [25-29]: Path: Path _id_ [25-29] (Ident _id_ [25-29] "Unit")
body: Block: Block _id_ [30-32]: <empty>"#]],
);
}
17 changes: 14 additions & 3 deletions language_service/src/code_lens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,18 @@ pub(crate) fn get_code_lenses(
let namespace = ns.name();
let range = into_range(position_encoding, decl.span, &user_unit.sources);
let name = decl.name.name.clone();
let is_test_case = decl.attrs.iter().any(|attr| *attr == qsc::hir::Attr::Test);

return Some((item, range, namespace, name, Some(item_id) == entry_item_id));
return Some((item, range, namespace, name, Some(item_id) == entry_item_id, is_test_case));
}
}
}
None
});

callables
.flat_map(|(item, range, namespace, name, is_entry_point)| {
if is_entry_point {
.flat_map(|(item, range, namespace, name, is_entry_point, is_test_case)| {
let mut lenses = if is_entry_point {
vec![
CodeLens {
range,
Expand Down Expand Up @@ -87,7 +88,17 @@ pub(crate) fn get_code_lenses(
}];
}
vec![]
};

if is_test_case {
lenses.push(CodeLens {
range,
command: CodeLensCommand::RunTest,
});
}

lenses

})
.collect()
}
Expand Down
1 change: 1 addition & 0 deletions language_service/src/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ fn collect_hardcoded_words(expected: WordKinds) -> Vec<Completion> {
),
Completion::new("Measurement".to_string(), CompletionItemKind::Interface),
Completion::new("Reset".to_string(), CompletionItemKind::Interface),
Completion::new("Test".to_string(), CompletionItemKind::Interface),
]);
}
HardcodedIdentKind::Size => {
Expand Down
1 change: 1 addition & 0 deletions language_service/src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ pub enum CodeLensCommand {
Run,
Estimate,
Circuit(Option<OperationInfo>),
RunTest,
}

#[derive(Debug)]
Expand Down
10 changes: 10 additions & 0 deletions npm/qsharp/src/compiler/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
IProgramConfig as wasmIProgramConfig,
TargetProfile,
type VSDiagnostic,
IProgramConfig,
} from "../../lib/web/qsc_wasm.js";
import { log } from "../log.js";
import {
Expand Down Expand Up @@ -77,6 +78,10 @@ export interface ICompiler {
exerciseSources: string[],
eventHandler: IQscEventTarget,
): Promise<boolean>;

collectTestCallables(
program: IProgramConfig,
): Promise<string[]>;
}

/**
Expand Down Expand Up @@ -243,6 +248,10 @@ export class Compiler implements ICompiler {

return success;
}

async collectTestCallables(program: IProgramConfig): Promise<string[]> {
return this.wasm.collect_test_callables(program);
}
}

/**
Expand Down Expand Up @@ -326,6 +335,7 @@ export const compilerProtocol: ServiceProtocol<ICompiler, QscEventData> = {
run: "requestWithProgress",
runWithPauliNoise: "requestWithProgress",
checkExerciseSolution: "requestWithProgress",
collectTestCallables: "request",
},
eventNames: ["DumpMachine", "Matrix", "Message", "Result"],
};
7 changes: 6 additions & 1 deletion npm/qsharp/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
} from "./language-service/language-service.js";
import { log } from "./log.js";
import { createProxy } from "./workers/node.js";
import type { ProjectLoader } from "../lib/web/qsc_wasm.js";
import type { IProgramConfig, ProjectLoader } from "../lib/web/qsc_wasm.js";
import { IProjectHost } from "./browser.js";

export { qsharpLibraryUriScheme };
Expand Down Expand Up @@ -91,4 +91,9 @@ export function getLanguageServiceWorker(): ILanguageServiceWorker {
);
}

export function collectTestCallables(config: IProgramConfig): string[] {
ensureWasm();
return wasm!.collect_test_callables(config);
}

sezna marked this conversation as resolved.
Show resolved Hide resolved
export * as utils from "./utils.js";
8 changes: 4 additions & 4 deletions vscode/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function basename(path: string): string | undefined {
return path.replace(/\/+$/, "").split("/").pop();
}

export function toVscodeRange(range: IRange): Range {
export function toVsCodeRange(range: IRange): Range {
return new Range(
range.start.line,
range.start.character,
Expand All @@ -42,7 +42,7 @@ export function toVscodeRange(range: IRange): Range {
}

export function toVscodeLocation(location: ILocation): any {
return new Location(Uri.parse(location.source), toVscodeRange(location.span));
return new Location(Uri.parse(location.source), toVsCodeRange(location.span));
}

export function toVscodeWorkspaceEdit(
Expand All @@ -52,7 +52,7 @@ export function toVscodeWorkspaceEdit(
for (const [source, edits] of iWorkspaceEdit.changes) {
const uri = vscode.Uri.parse(source, true);
const vsEdits = edits.map((edit) => {
return new vscode.TextEdit(toVscodeRange(edit.range), edit.newText);
return new vscode.TextEdit(toVsCodeRange(edit.range), edit.newText);
});
workspaceEdit.set(uri, vsEdits);
}
Expand All @@ -73,7 +73,7 @@ export function toVsCodeDiagnostic(d: VSDiagnostic): vscode.Diagnostic {
break;
}
const vscodeDiagnostic = new vscode.Diagnostic(
toVscodeRange(d.range),
toVsCodeRange(d.range),
d.message,
severity,
);
Expand Down
4 changes: 2 additions & 2 deletions vscode/src/debugger/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
log,
} from "qsharp-lang";
import { updateCircuitPanel } from "../circuit";
import { basename, isQsharpDocument, toVscodeRange } from "../common";
import { basename, isQsharpDocument, toVsCodeRange } from "../common";
import {
DebugEvent,
EventType,
Expand Down Expand Up @@ -134,7 +134,7 @@ export class QscDebugSession extends LoggingDebugSession {
),
};
return {
range: toVscodeRange(location.range),
range: toVsCodeRange(location.range),
uiLocation,
breakpoint: this.createBreakpoint(location.id, uiLocation),
} as IBreakpointLocationData;
Expand Down
2 changes: 2 additions & 0 deletions vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { initCodegen } from "./qirGeneration.js";
import { activateTargetProfileStatusBarItem } from "./statusbar.js";
import { initTelemetry } from "./telemetry.js";
import { registerWebViewCommands } from "./webviewPanel.js";
import { initTestExplorer } from "./testExplorer.js";

export async function activate(
context: vscode.ExtensionContext,
Expand Down Expand Up @@ -75,6 +76,7 @@ export async function activate(

context.subscriptions.push(...registerQSharpNotebookHandlers());

initTestExplorer(context);
initAzureWorkspaces(context);
initCodegen(context);
activateDebugger(context);
Expand Down
9 changes: 7 additions & 2 deletions vscode/src/language-service/codeLens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
qsharpLibraryUriScheme,
} from "qsharp-lang";
import * as vscode from "vscode";
import { toVscodeRange } from "../common";
import { toVsCodeRange } from "../common";
sezna marked this conversation as resolved.
Show resolved Hide resolved

export function createCodeLensProvider(languageService: ILanguageService) {
return new QSharpCodeLensProvider(languageService);
Expand Down Expand Up @@ -69,9 +69,14 @@
args = [cl.args];
}
break;
case "runTest":
title = "Run Test",

Check failure on line 73 in vscode/src/language-service/codeLens.ts

View workflow job for this annotation

GitHub Actions / Check web files

Expected an assignment or function call and instead saw an expression
command = "qsharp-vscode.runTest";
tooltip = "Run test";
break;
}

return new vscode.CodeLens(toVscodeRange(cl.range), {
return new vscode.CodeLens(toVsCodeRange(cl.range), {
title,
command,
arguments: args,
Expand Down
4 changes: 2 additions & 2 deletions vscode/src/language-service/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { ILanguageService, samples } from "qsharp-lang";
import * as vscode from "vscode";
import { CompletionItem } from "vscode";
import { toVscodeRange } from "../common";
import { toVsCodeRange } from "../common";
import { EventType, sendTelemetryEvent } from "../telemetry";

export function createCompletionItemProvider(
Expand Down Expand Up @@ -84,7 +84,7 @@ class QSharpCompletionItemProvider implements vscode.CompletionItemProvider {
item.sortText = c.sortText;
item.detail = c.detail;
item.additionalTextEdits = c.additionalTextEdits?.map((edit) => {
return new vscode.TextEdit(toVscodeRange(edit.range), edit.newText);
return new vscode.TextEdit(toVsCodeRange(edit.range), edit.newText);
});
return item;
});
Expand Down
4 changes: 2 additions & 2 deletions vscode/src/language-service/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { ILanguageService } from "qsharp-lang";
import * as vscode from "vscode";
import { toVscodeRange } from "../common";
import { toVsCodeRange } from "../common";
import { EventType, FormatEvent, sendTelemetryEvent } from "../telemetry";
import { getRandomGuid } from "../utils";

Expand Down Expand Up @@ -50,7 +50,7 @@ class QSharpFormattingProvider
}

let edits = lsEdits.map(
(edit) => new vscode.TextEdit(toVscodeRange(edit.range), edit.newText),
(edit) => new vscode.TextEdit(toVsCodeRange(edit.range), edit.newText),
);

if (range) {
Expand Down
4 changes: 2 additions & 2 deletions vscode/src/language-service/hover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { ILanguageService } from "qsharp-lang";
import * as vscode from "vscode";
import { toVscodeRange } from "../common";
import { toVsCodeRange } from "../common";

export function createHoverProvider(languageService: ILanguageService) {
return new QSharpHoverProvider(languageService);
Expand All @@ -21,7 +21,7 @@ class QSharpHoverProvider implements vscode.HoverProvider {
hover &&
new vscode.Hover(
new vscode.MarkdownString(hover.contents),
toVscodeRange(hover.span),
toVsCodeRange(hover.span),
)
);
}
Expand Down
4 changes: 2 additions & 2 deletions vscode/src/language-service/rename.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { ILanguageService } from "qsharp-lang";
import * as vscode from "vscode";
import { toVscodeRange, toVscodeWorkspaceEdit } from "../common";
import { toVsCodeRange, toVscodeWorkspaceEdit } from "../common";

export function createRenameProvider(languageService: ILanguageService) {
return new QSharpRenameProvider(languageService);
Expand Down Expand Up @@ -40,7 +40,7 @@ class QSharpRenameProvider implements vscode.RenameProvider {
);
if (prepareRename) {
return {
range: toVscodeRange(prepareRename.range),
range: toVsCodeRange(prepareRename.range),
placeholder: prepareRename.newText,
};
} else {
Expand Down
Loading
Loading