Skip to content

Commit

Permalink
add qtest library
Browse files Browse the repository at this point in the history
  • Loading branch information
sezna committed Nov 14, 2024
1 parent 97385cd commit 6d9a518
Show file tree
Hide file tree
Showing 7 changed files with 290 additions and 37 deletions.
10 changes: 10 additions & 0 deletions library/qtest/qsharp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"author": "Microsoft",
"license": "MIT",
"files": [
"src/Main.qs",
"src/Operations.qs",
"src/Functions.qs",
"src/Tests.qs"
]
}
112 changes: 112 additions & 0 deletions library/qtest/src/Functions.qs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

/// # Summary
/// Runs a series of named test functions with expected results.
///
/// # Description
/// Given an array of test names, test functions, and expected results (of the same type), returns `true`
/// if all tests passed, and `false` if any one test failed.
/// Prints the expected and received result for any failed tests.
///
/// # Input
/// ## test_cases
/// An array of three-arity tuples of the form `(test_name, callable_to_test, expected_result)`.
/// `callable_to_test` will be called and its result will be compared to `expected_result`.
///
/// # Example
/// ```qsharp
/// function Main() : Unit {
/// TestCases([
/// ("Should return 42", TestCaseOne, 42),
/// ("Should add one", () -> AddOne(5), 6)
/// ]);
/// }
///
/// function TestCaseOne() : Int {
/// 42
/// }
///
/// function AddOne(x: Int) : Int {
/// x + 1
/// }
/// ```
function TestCases<'Result : Eq + Show > (test_cases : (String, () -> 'Result, 'Result)[]) : Bool {
let failed_test_buf = TestCasesSilent(test_cases);

if Length(failed_test_buf) == 0 {
Message($"{Length(test_cases)} test(s) passed.");
true
} else {
Message($"{Length(failed_test_buf)} tests failed.");
for item in failed_test_buf {
Message($"{item}")
}
false
}
}

/// # Summary
/// Similar to `Qtest.Functions.TestCases`, but returns test failure info in an array of strings instead of directly messaging to
/// output. Useful if you want to handle your own messaging when writing tests, for CI or similar.

/// See `Qtest.Functions.TestCases` for more details.
///
/// # Description
/// Given an array of test names, test functions, and expected results (of the same type), returns an array
/// of strings representing all failed test cases (if any). Strings are of the form "test_name: expected {}, got {}"
///
/// # Input
/// ## test_cases
/// An array of three-arity tuples of the form `(test_name, callable_to_test, expected_result)`.
/// `callable_to_test` will be called and its result will be compared to `expected_result`.
///
/// # Example
/// ```qsharp
/// function Main() : Unit {
/// let failure_messages = TestCasesSilent([
/// ("Should return 42", TestCaseOne, 42),
/// ("Should add one", () -> AddOne(5), 6)
/// ]);
/// Std.Diagnostics.Fact(Length(failure_messages) == 0, "No tests should fail.")
/// }
///
/// function TestCaseOne() : Int {
/// 42
/// }
///
/// function AddOne(x: Int) : Int {
/// x + 1
/// }
/// ```
function TestCasesSilent<'Result : Eq + Show > (test_cases : (String, () -> 'Result, 'Result)[]) : String[] {
let num_tests = Length(test_cases);
mutable failed_test_buf = [];

for (name, case, result) in test_cases {
let (did_pass, message) = TestCase(case, result)!;
if not did_pass {
set failed_test_buf = failed_test_buf + [$"{name}: {message}"];
}
}

failed_test_buf
}

struct TestCaseResult {
did_pass : Bool,
message : String,
}

function TestCase<'Result : Eq + Show > (test_case : () -> 'Result, expected : 'Result) : TestCaseResult {
let result = test_case();
if result == expected {
new TestCaseResult { did_pass = true, message = "" }
} else {
new TestCaseResult { did_pass = false, message = $"expected: {expected}, got: {result}" }
}
}



export TestCases;
4 changes: 4 additions & 0 deletions library/qtest/src/Main.qs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

export Functions, Operations;
116 changes: 116 additions & 0 deletions library/qtest/src/Operations.qs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

/// # Summary
/// Runs a series of named test operations with expected results.
///
/// # Description
/// Given an array of test names, test operations, and expected results (of the same type), returns `true`
/// if all tests passed, and `false` if any one test failed.
/// Prints the expected and received result for any failed tests.
///
/// # Input
/// ## test_cases
/// An array of five-arity tuples of the form `(test_name, num_qubits, qubit_prep_callable, callable_to_test, expected_result)`.
/// `num_qubits` will be allocated in a qubit array and passed to `qubit_prep_callable` to prepare the state before executing
/// `callable_to_test`. Afterwards, `callable_to_test` will be called and its result will be compared to `expected_result`.
///
/// # Example
/// ```qsharp
/// function Main() : Unit {
/// TestCases([
/// ("Should return 42", TestCaseOne, 42),
/// ("Should add one", () => AddOne(5), 6)
/// ]);
/// }
///
/// function TestCaseOne() : Int {
/// 42
/// }
///
/// function AddOne(x: Int) : Int {
/// x + 1
/// }
/// ```
operation TestCases<'Result : Eq + Show > (test_cases : (String, Int, (Qubit[]) => (), (Qubit[]) => 'Result, 'Result)[]) : Bool {
let failed_test_buf = TestCasesSilent(test_cases);

if Length(failed_test_buf) == 0 {
Message($"{Length(test_cases)} test(s) passed.");
true
} else {
Message($"{Length(failed_test_buf)} tests failed.");
for item in failed_test_buf {
Message($"{item}")
}
false
}
}

/// # Summary
/// Similar to `Qtest.Operations.TestCases`, but returns test failure info in an array of strings instead of directly messaging to
/// output. Useful if you want to handle your own messaging when writing tests, for CI or similar.

/// See `Qtest.Operations.TestCases` for more details.
///
/// # Description
/// Given an array of test names, test functions, and expected results (of the same type), returns an array
/// of strings representing all failed test cases (if any). Strings are of the form "test_name: expected {}, got {}"
///
/// # Input
/// ## test_cases
/// An array of five-arity tuples of the form `(test_name, num_qubits, qubit_prep_callable, callable_to_test, expected_result)`.
/// `num_qubits` will be allocated in a qubit array and passed to `qubit_prep_callable` to prepare the state before executing
/// `callable_to_test`. Afterwards, `callable_to_test` will be called and its result will be compared to `expected_result`.
///
/// # Example
/// ```qsharp
/// function Main() : Unit {
/// let failure_messages = TestCasesSilent([
/// ("Should return 42", TestCaseOne, 42),
/// ("Should add one", () => AddOne(5), 6)
/// ]);
/// Std.Diagnostics.Fact(Length(failure_messages) == 0, "No tests should fail.")
/// }
///
/// function TestCaseOne() : Int {
/// 42
/// }
///
/// function AddOne(x: Int) : Int {
/// x + 1
/// }
/// ```
operation TestCasesSilent<'Result : Eq + Show > (test_cases : (String, Int, (Qubit[]) => (), (Qubit[]) => 'Result, 'Result)[]) : String[] {
let num_tests = Length(test_cases);
mutable failed_test_buf = [];

for (name, num_qubits, prepare_state, case, result) in test_cases {
use qubits = Qubit[num_qubits];
prepare_state(qubits);
let (did_pass, message) = TestCase(qubits, case, result)!;
if not did_pass {
set failed_test_buf = failed_test_buf + [$"{name}: {message}"];
}
ResetAll(qubits);
}

failed_test_buf
}
struct TestCaseResult {
did_pass : Bool,
message : String,
}

operation TestCase<'Result : Eq + Show > (qubits: Qubit[], test_case : (Qubit[]) => 'Result, expected : 'Result) : TestCaseResult {
let result = test_case(qubits);
if result == expected {
new TestCaseResult { did_pass = true, message = "" }
} else {
new TestCaseResult { did_pass = false, message = $"expected: {expected}, got: {result}" }
}
}



export TestCases;
26 changes: 26 additions & 0 deletions library/qtest/src/Tests.qs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
function Main() : Unit {
Std.Diagnostics.Fact(
Functions.TestCases([
("Should return 42", TestCaseOne, 42),
("Should add one", () -> AddOne(5), 6)
]),
"Test harness failed to return true for all passing tests."
);
Std.Diagnostics.Fact(
Length(Functions.TestCasesSilent([
("Should return 42", TestCaseOne, 43),
("Should add one", () -> AddOne(5), 42)
])) == 2,
"Test harness failed to return messages for failing tests."
);
}

function TestCaseOne() : Int {
42
}

function AddOne(x : Int) : Int {
x + 1
}
8 changes: 8 additions & 0 deletions library/signed/qsharp.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
"ref": "3195043",
"path": "library/unstable"
}
},
"Qtest": {
"github": {
"owner": "Microsoft",
"repo": "qsharp",
"ref": "b408eddb",
"path": "library/qtest"
}
}
},
"files": [
Expand Down
51 changes: 14 additions & 37 deletions library/signed/src/Tests.qs
Original file line number Diff line number Diff line change
Expand Up @@ -8,47 +8,24 @@ import Measurement.MeasureSignedInteger;
/// This entrypoint runs tests for the signed integer library.
operation Main() : Unit {
UnsignedOpTests();
MeasureSignedIntTests();
Qtest.Operations.TestCases(MeasureSignedIntTests());
SignedOpTests();

}

operation MeasureSignedIntTests() : Unit {
use a = Qubit[4];

// 0b0001 == 1
X(a[0]);
let res = MeasureSignedInteger(a, 4);
Fact(res == 1, $"Expected 1, received {res}");

// 0b1111 == -1
X(a[0]);
X(a[1]);
X(a[2]);
X(a[3]);
let res = MeasureSignedInteger(a, 4);
Fact(res == -1, $"Expected -1, received {res}");

// 0b01000 == 8
use a = Qubit[5];
X(a[3]);
let res = MeasureSignedInteger(a, 5);
Fact(res == 8, $"Expected 8, received {res}");

// 0b11110 == -2
X(a[1]);
X(a[2]);
X(a[3]);
X(a[4]);
let res = MeasureSignedInteger(a, 5);
Fact(res == -2, $"Expected -2, received {res}");

// 0b11000 == -8
X(a[3]);
X(a[4]);
let res = MeasureSignedInteger(a, 5);
Fact(res == -8, $"Expected -8, received {res}");

function MeasureSignedIntTests() : (String, Int, (Qubit[]) => (), (Qubit[]) => Int, Int)[] {
[
("0b0001 == 1", 4, (qs) => X(qs[0]), (qs) => MeasureSignedInteger(qs, 4), 1),
("0b1111 == -1", 4, (qs) => { X(qs[0]); X(qs[1]); X(qs[2]); X(qs[3]); }, (qs) => MeasureSignedInteger(qs, 4), -1),
("0b01000 == 8", 5, (qs) => X(qs[3]), (qs) => MeasureSignedInteger(qs, 5), 8),
("0b11110 == -2", 5, (qs) => {
X(qs[1]);
X(qs[2]);
X(qs[3]);
X(qs[4]);
}, (qs) => MeasureSignedInteger(qs, 5), -2),
("0b11000 == -8", 5, (qs) => { X(qs[3]); X(qs[4]); }, (qs) => MeasureSignedInteger(qs, 5), -8)
]
}

operation SignedOpTests() : Unit {
Expand Down

0 comments on commit 6d9a518

Please sign in to comment.