Skip to content
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f497801
Implement `panic` expression
ironcev Feb 24, 2025
f0134df
Merge branch 'master' into ironcev/panic-expression
ironcev Apr 8, 2025
51719ed
Fix failing SDK harness test
ironcev Apr 8, 2025
941850c
Fix failing LSP test
ironcev Apr 8, 2025
22d0e81
Fix failing LSP test
ironcev Apr 8, 2025
5e89cf7
Merge branch 'master' into ironcev/panic-expression
ironcev Apr 8, 2025
de30afb
Merge branch 'master' into ironcev/panic-expression
ironcev Apr 11, 2025
1c59376
Do not remove gas costs in `forc test` snapshots
ironcev Apr 13, 2025
e2ba203
Merge branch 'master' into ironcev/panic-expression
ironcev Apr 14, 2025
7dddd03
Merge branch 'master' into ironcev/panic-expression
ironcev Apr 14, 2025
8cb0ff3
Merge branch 'master' into ironcev/panic-expression
ironcev Apr 14, 2025
d164d87
Merge branch 'master' into ironcev/panic-expression
ironcev Apr 15, 2025
f4ee727
Merge branch 'master' into ironcev/panic-expression
ironcev Apr 16, 2025
43bb8cb
Merge branch 'master' into ironcev/panic-expression
ironcev Apr 17, 2025
39c08d5
Merge branch 'master' into ironcev/panic-expression
ironcev Apr 23, 2025
8137f09
Fix merge issues
ironcev Apr 23, 2025
b977f43
Fix wording issues and a typo
ironcev Apr 23, 2025
2605b7e
Fix fmt issues
ironcev Apr 23, 2025
4e25ac5
Merge branch 'master' into ironcev/panic-expression
ironcev Apr 24, 2025
04e9a6f
Fix SDK harness test failing compilation
ironcev Apr 24, 2025
de1de34
Adjust gas in snapshot tests snaps
ironcev Apr 24, 2025
9c485ad
Merge branch 'master' into ironcev/panic-expression
ironcev Apr 27, 2025
2f841ff
Fix flaky SDK harness test by using `send_transaction_and_await_commit`
ironcev Apr 28, 2025
c0ddc1e
Merge branch 'master' into ironcev/panic-expression
ironcev Apr 29, 2025
973c95f
Put `panic` keyword behind the feature flag
ironcev Apr 29, 2025
86a7655
Fix fmt issue
ironcev Apr 29, 2025
f834a60
Temporarily disable SDK harness test for `panic` in predicates
ironcev Apr 29, 2025
b11f629
Remove `predicate_panic_expression` Rust module from SDK harness
ironcev Apr 29, 2025
02733a4
Merge branch 'master' into ironcev/panic-expression
ironcev Apr 29, 2025
882f9d2
Merge branch 'master' into ironcev/panic-expression
ironcev Apr 30, 2025
cd86555
Fix merge issues
ironcev Apr 30, 2025
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
72 changes: 50 additions & 22 deletions docs/book/src/testing/unit-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,16 @@ fn test_meaning_of_life() {

Tests with `#[test(should_revert)]` are considered to be passing if they are reverting.

Revert codes are not shown by default in passing tests that have `should_revert`. To see revert codes, use the `--revert-codes` flag, `forc test --revert-codes`:

```console
test test_meaning_of_life ... ok (23.099µs, 0 gas)
Revert code: ffffffffffff0004
```

## Calling Contracts

Unit tests can call contract functions an example for such calls can be seen below.
Unit tests can call contract functions. An example for such calls can be seen below.

```sway
contract;
Expand All @@ -96,7 +103,7 @@ To test the `test_function()`, a unit test like the following can be written.
fn test_success() {
let caller = abi(MyContract, CONTRACT_ID);
let result = caller.test_function {}();
assert(result == true)
assert(result == true);
}
```

Expand All @@ -107,7 +114,7 @@ It is also possible to test failure with contract calls as well.
fn test_fail() {
let caller = abi(MyContract, CONTRACT_ID);
let result = caller.test_function {}();
assert(result == false)
assert(result == false);
}
```

Expand Down Expand Up @@ -154,37 +161,58 @@ fn main() {}

#[test]
fn test_fn() {
let a = 10;
let a = 10;
log(a);
let b = 30;
log(b);
assert_eq(a, 10)
assert_eq(b, 30)
assert_eq(a, 10);
assert_eq(b, 30);
}
```

The example shown above is logging two different variables, `a` and `b` and their values are `10` and `30`, respectively. Without log decoding printed log for this test with `forc test --logs` (`--logs` flag is required to see the logs for this example since the test is passing. Logs are silenced by default in passing tests, and can be enabled using the `--logs` flag.):
The above example shows a passing test that is logging two different variables, `a` and `b`, and their values are `10` and `30`, respectively. Logs are silenced by default in passing tests, and can be enabled using the `--logs` flag, `forc test --logs`:

```console
Finished debug [unoptimized + fuel] target(s) in 5.23s
Bytecode hash: 0x1cb1edc031691c5c08b50fd0f07b02431848ab81b325b72eb3fd233c67d6b548
Running 1 test, filtered 0 tests
test test_fn ... ok (38.875µs, 232 gas)
[{"LogData":{"data":"000000000000000a","digest":"8d85f8467240628a94819b26bee26e3a9b2804334c63482deacec8d64ab4e1e7","id":"0000000000000000000000000000000000000000000000000000000000000000","is":10368,"len":8,"pc":11032,"ptr":67107840,"ra":0,"rb":0}},{"LogData":{"data":"000000000000001e","digest":"48a97e421546f8d4cae1cf88c51a459a8c10a88442eed63643dd263cef880c1c","id":"0000000000000000000000000000000000000000000000000000000000000000","is":10368,"len":8,"pc":11516,"ptr":67106816,"ra":0,"rb":1}}]
Running 1 test, filtered 0 tests
test test_fn ... ok (58.842µs, 0 gas)
Decoded log value: 10, log rb: 1515152261580153489
Decoded log value: 30, log rb: 1515152261580153489
```

This is not very easy to understand, it is possible to decode these logs with `--decode` flag, executing `forc test --logs --decode`:
The `--logs` flag prints decoded log values. If you want to see pretty-printed raw log receipts you can use the `--raw-logs --pretty` flags, `forc test --raw-logs --pretty`:

```console
Finished debug [unoptimized + fuel] target(s) in 5.23s
Bytecode hash: 0x1cb1edc031691c5c08b50fd0f07b02431848ab81b325b72eb3fd233c67d6b548
Running 1 test, filtered 0 tests
test test_fn ... ok (38.875µs, 232 gas)
Decoded log value: 10, log rb: 0
Decoded log value: 30, log rb: 1
test test_fn ... ok (54.042µs, 0 gas)
Raw logs:
[
{
"LogData": {
"data": "000000000000000a",
"digest": "8d85f8467240628a94819b26bee26e3a9b2804334c63482deacec8d64ab4e1e7",
"id": "0000000000000000000000000000000000000000000000000000000000000000",
"is": 10368,
"len": 8,
"pc": 11212,
"ptr": 67107840,
"ra": 0,
"rb": 1515152261580153489
}
},
{
"LogData": {
"data": "000000000000001e",
"digest": "48a97e421546f8d4cae1cf88c51a459a8c10a88442eed63643dd263cef880c1c",
"id": "0000000000000000000000000000000000000000000000000000000000000000",
"is": 10368,
"len": 8,
"pc": 11212,
"ptr": 67106816,
"ra": 0,
"rb": 1515152261580153489
}
}
]
```

As it can be seen, the values are human readable and easier to understand which makes debugging much more easier.

**Note**: This is an experimental feature and we are actively working on reporting variable names next to their values.
The `--logs` and `--raw-logs` flags can be combined to print both the decoded and raw logs.
<!-- unit_test_log::example::end -->
3 changes: 3 additions & 0 deletions forc-plugins/forc-migrate/src/visiting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,9 @@ impl __ProgramVisitor {
Self::visit_expr(ctx, lexed_returned.__as_ref(), ty_returned, visitor, output)?;
}
}
Expr::Panic { expr_opt: _, .. } => {
// TODO: Implement visiting `panic`.
}
Expr::If(if_expr) => {
Self::visit_if(ctx, if_expr, ty_expr, visitor, output)?;
}
Expand Down
11 changes: 11 additions & 0 deletions forc/src/cli/commands/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ pub struct TestPrintOpts {
/// Print the raw logs for tests.
#[clap(long)]
pub raw_logs: bool,
/// Print the revert codes for tests.
#[clap(long)]
pub revert_codes: bool,
}

pub(crate) fn exec(cmd: Command) -> ForcResult<()> {
Expand Down Expand Up @@ -171,11 +174,19 @@ fn print_tested_pkg(pkg: &TestedPackage, test_print_opts: &TestPrintOpts) -> For
}
}

// If raw logs are enabled, print them.
if test_print_opts.raw_logs {
let formatted_logs = format_log_receipts(logs, test_print_opts.pretty_print)?;
info!("Raw logs:\n{}", formatted_logs);
}

// If revert codes are enabled, print them.
if test_print_opts.revert_codes {
if let Some(revert_code) = test.revert_code() {
info!("Revert code: {revert_code:x}");
}
}

// If the test is failing, save the test result for printing the details later on.
if !test_passed {
failed_tests.push(test);
Expand Down
17 changes: 17 additions & 0 deletions sway-ast/src/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ pub enum Expr {
return_token: ReturnToken,
expr_opt: Option<Box<Expr>>,
},
Panic {
panic_token: PanicToken,
expr_opt: Option<Box<Expr>>,
},
If(IfExpr),
Match {
match_token: MatchToken,
Expand Down Expand Up @@ -223,6 +227,17 @@ impl Spanned for Expr {
};
Span::join(start, &end)
}
Expr::Panic {
panic_token,
expr_opt,
} => {
let start = panic_token.span();
let end = match expr_opt {
Some(expr) => expr.span(),
None => panic_token.span(),
};
Span::join(start, &end)
}
Expr::If(if_expr) => if_expr.span(),
Expr::Match {
match_token,
Expand Down Expand Up @@ -566,6 +581,7 @@ impl Expr {
| Expr::Parens(..)
| Expr::Array(..)
| Expr::Return { .. }
| Expr::Panic { .. }
| Expr::FuncApp { .. }
| Expr::Index { .. }
| Expr::MethodCall { .. }
Expand Down Expand Up @@ -613,6 +629,7 @@ impl Expr {
Expr::Array(_) => "array",
Expr::Asm(_) => "assembly block",
Expr::Return { .. } => "return",
Expr::Panic { .. } => "panic",
Expr::If(_) => "if expression",
Expr::Match { .. } => "match expression",
Expr::While { .. } => "while loop",
Expand Down
1 change: 1 addition & 0 deletions sway-ast/src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ define_keyword!(ConfigurableToken, "configurable");
define_keyword!(TypeToken, "type");
define_keyword!(PtrToken, "__ptr");
define_keyword!(SliceToken, "__slice");
define_keyword!(PanicToken, "panic");

/// The type is a token.
pub trait Token: Spanned + Sized {
Expand Down
2 changes: 1 addition & 1 deletion sway-ast/src/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub enum Pattern {
/// A pattern made of a single ident, which could either be a variable or an enum variant
AmbiguousSingleIdent(Ident),
Var {
reference: Option<RefToken>, // TODO-IG: Implement `ref`, `mut`, and `ref mut` when implementing matching of references.
reference: Option<RefToken>, // TODO: (REFERENCES) Implement `ref`, `mut`, and `ref mut` when implementing matching of references.
mutable: Option<MutToken>,
name: Ident,
},
Expand Down
2 changes: 1 addition & 1 deletion sway-core/src/abi_generation/abi_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ impl TypeInfo {
referenced_type,
} => {
format!(
"__ref {}{}", // TODO-IG: No references in ABIs according to the RFC. Or we want to have them?
"__ref {}{}", // TODO: (REFERENCES) No references in ABIs according to the RFC. Or we want to have them?
if *to_mutable_value { "mut " } else { "" },
referenced_type.abi_str(ctx, engines, false)
)
Expand Down
2 changes: 1 addition & 1 deletion sway-core/src/abi_generation/evm_abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ pub fn abi_str(type_info: &TypeInfo, engines: &Engines) -> String {
referenced_type,
} => {
format!(
"__ref {}{}", // TODO-IG: No references in ABIs according to the RFC. Or we want to have them?
"__ref {}{}", // TODO: (REFERENCES) No references in ABIs according to the RFC. Or we want to have them?
if *to_mutable_value { "mut " } else { "" },
abi_str_type_arg(referenced_type, engines)
)
Expand Down
2 changes: 1 addition & 1 deletion sway-core/src/asm_generation/fuel/fuel_asm_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2094,7 +2094,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> {

// ---------------------------------------------------------------------------------------------

// TODO-IG: Reassess all the places we use `is_copy_type`.
// TODO: (REFERENCES) Reassess all the places where we use `is_copy_type`.
pub(crate) fn is_copy_type(&self, ty: &Type) -> bool {
ty.is_unit(self.context)
|| ty.is_never(self.context)
Expand Down
10 changes: 9 additions & 1 deletion sway-core/src/control_flow_analysis/analyze_return_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,11 @@ impl<'cfg> ControlFlowGraph<'cfg> {
enum NodeConnection {
/// This represents a node that steps on to the next node.
NextStep(Option<NodeIndex>),
/// This represents a return or implicit return node, which aborts the stepwise flow.
/// This represents a node which aborts the stepwise flow.
/// Such nodes are:
/// - return expressions,
/// - implicit returns,
/// - panic expressions.
Return(NodeIndex),
}

Expand All @@ -154,6 +158,10 @@ fn connect_node<'eng: 'cfg, 'cfg>(
| ty::TyAstNodeContent::Expression(ty::TyExpression {
expression: ty::TyExpressionVariant::ImplicitReturn(..),
..
})
| ty::TyAstNodeContent::Expression(ty::TyExpression {
expression: ty::TyExpressionVariant::Panic(..),
..
}) => {
let this_index = graph.add_node(ControlFlowGraphNode::from_node(node));
if let Some(leaf_ix) = leaf_opt {
Expand Down
17 changes: 13 additions & 4 deletions sway-core/src/control_flow_analysis/dead_code_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2117,8 +2117,17 @@ fn connect_expression<'eng: 'cfg, 'cfg>(
options,
)
}
ImplicitReturn(exp) | Return(exp) => {
let this_index = graph.add_node("return entry".into());
ImplicitReturn(exp) | Return(exp) | Panic(exp) => {
let return_type = match expr_variant {
ImplicitReturn(_) => "implicit return",
Return(_) => "return",
Panic(_) => "panic",
_ => unreachable!(
"the `expr_variant` is checked to be `ImplicitReturn`, `Return`, or `Panic`"
),
};

let this_index = graph.add_node(format!("{return_type} entry").into());
for leaf in leaves {
graph.add_edge(*leaf, this_index, "".into());
}
Expand All @@ -2133,14 +2142,14 @@ fn connect_expression<'eng: 'cfg, 'cfg>(
exp.span.clone(),
options,
)?;
if let Return(_) = expr_variant {
if let Return(_) | Panic(_) = expr_variant {
// TODO: is this right? Shouldn't we connect the return_contents leaves to the exit
// node?
for leaf in return_contents {
graph.add_edge(this_index, leaf, "".into());
}
if let Some(exit_node) = exit_node {
graph.add_edge(this_index, exit_node, "return".into());
graph.add_edge(this_index, exit_node, return_type.into());
}
Ok(vec![])
} else {
Expand Down
2 changes: 1 addition & 1 deletion sway-core/src/ir_generation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ impl CompiledFunctionCache {
name: Ident::new(Span::from_string(format!(
"{}_{}",
decl.name,
context.get_unique_id()
context.get_unique_symbol_id()
))),
parameters: decl.parameters.clone(),
..decl.clone()
Expand Down
5 changes: 5 additions & 0 deletions sway-core/src/ir_generation/const_eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,11 @@ fn const_eval_typed_expr(
span: exp.span.clone(),
});
}
ty::TyExpressionVariant::Panic(exp) => {
return Err(ConstEvalError::CannotBeEvaluatedToConst {
span: exp.span.clone(),
});
}
ty::TyExpressionVariant::MatchExp { desugared, .. } => {
const_eval_typed_expr(lookup, known_consts, desugared)?
}
Expand Down
Loading
Loading