Skip to content

Commit f53b948

Browse files
committed
feat: ability to generate code coverage
1 parent 9e873af commit f53b948

File tree

9 files changed

+85
-25
lines changed

9 files changed

+85
-25
lines changed

.dockerignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
target/
2+
examples/

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "clarinet"
3-
version = "0.9.3"
3+
version = "0.10.0"
44
authors = ["Ludo Galabru <ludovic@galabru.com>"]
55
edition = "2018"
66
description = "Clarinet is a clarity runtime packaged as a command line tool, designed to facilitate smart contract understanding, development, testing and deployment."
@@ -36,7 +36,7 @@ deno_runtime = "=0.9.3"
3636
# deno_web = "=0.31.0"
3737
rusty_v8 = "=0.20.0"
3838
# clarity_repl = { package = "clarity-repl", path = "../../clarity-repl", features = ["cli"] }
39-
clarity_repl = { package = "clarity-repl", version = "=0.11.1" }
39+
clarity_repl = { package = "clarity-repl", version = "=0.12.0" }
4040
bip39 = "1.0.0-rc1"
4141
aes = "0.6.0"
4242
base64 = "0.13.0"

Dockerfile

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
FROM rust:stretch as build
2+
3+
ARG STACKS_NODE_VERSION="No Version Info"
4+
ARG GIT_BRANCH='No Branch Info'
5+
ARG GIT_COMMIT='No Commit Info'
6+
7+
WORKDIR /src
8+
9+
COPY . .
10+
11+
RUN mkdir /out
12+
13+
RUN apt update && apt install ca-certificates
14+
15+
RUN rustup update stable
16+
17+
RUN rustup default stable-aarch64-unknown-linux-gnu
18+
19+
RUN cargo build --release --locked
20+
21+
RUN cp target/release/clarinet /out
22+
23+
FROM debian:stretch-slim
24+
25+
COPY --from=build /out/ /bin/
26+
27+
ENTRYPOINT ["clarinet"]

src/console/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ pub fn load_session(start_repl: bool, env: String) -> Result<repl::SessionSettin
6060
.initial_contracts
6161
.push(repl::settings::InitialContract {
6262
code: code,
63+
path: contract_path.to_str().unwrap().into(),
6364
name: Some(name.clone()),
6465
deployer: deployer_address.clone(),
6566
});

src/frontend/cli.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ struct Test {
110110
/// Print debug info
111111
#[clap(short = 'd')]
112112
pub debug: bool,
113+
/// Generate coverage
114+
#[clap(long = "coverage")]
115+
pub coverage: bool,
116+
/// Files to includes
113117
pub files: Vec<String>,
114118
}
115119

@@ -225,7 +229,7 @@ pub fn main() {
225229
println!("{}", e);
226230
return;
227231
}
228-
run_tests(test.files);
232+
run_tests(test.files, test.coverage);
229233
},
230234
Command::Deploy(deploy) => {
231235
let start_repl = false;

src/generators/contract.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ impl GetChangesForNewContract {
6666
fn create_template_test(&mut self) {
6767
let content = format!(
6868
r#"
69-
import {{ Clarinet, Tx, Chain, Account, types }} from 'https://deno.land/x/clarinet@v0.6.0/index.ts';
69+
import {{ Clarinet, Tx, Chain, Account, types }} from 'https://deno.land/x/clarinet@v0.10.0/index.ts';
7070
import {{ assertEquals }} from 'https://deno.land/std@0.90.0/testing/asserts.ts';
7171
7272
Clarinet.test({{

src/test/deno.rs

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use deno_core::ModuleSpecifier;
2222
use deno_runtime::web_worker::WebWorkerOptions;
2323
use deno_runtime::ops::worker_host::CreateWebWorkerCb;
2424
use deno_runtime::web_worker::WebWorker;
25+
use clarity_repl::clarity::coverage::CoverageReporter;
2526

2627
mod sessions {
2728
use std::sync::Mutex;
@@ -35,10 +36,10 @@ mod sessions {
3536
use super::TransactionArgs;
3637

3738
lazy_static! {
38-
static ref SESSIONS: Mutex<HashMap<u32, Session>> = Mutex::new(HashMap::new());
39+
pub static ref SESSIONS: Mutex<HashMap<u32, (String, Session)>> = Mutex::new(HashMap::new());
3940
}
4041

41-
pub fn handle_setup_chain(transactions: Vec<TransactionArgs>) -> Result<(u32, Vec<Account>), AnyError> {
42+
pub fn handle_setup_chain(name: String, transactions: Vec<TransactionArgs>) -> Result<(u32, Vec<Account>), AnyError> {
4243
let mut sessions = SESSIONS.lock().unwrap();
4344
let session_id = sessions.len() as u32;
4445

@@ -81,11 +82,11 @@ mod sessions {
8182
.initial_contracts
8283
.push(repl::settings::InitialContract {
8384
code: deploy_contract.code.clone(),
85+
path: "".into(),
8486
name: Some(deploy_contract.name.clone()),
8587
deployer,
8688
});
8789
}
88-
8990
// if let Some(ref contract_call) tx.contract_call {
9091
// TODO: initial_tx_sender
9192
// let code = format!("(contract-call? '{}.{} {} {})", initial_tx_sender, contract_call.contract, contract_call.method, contract_call.args.join(" "));
@@ -99,6 +100,7 @@ mod sessions {
99100
// }
100101
}
101102

103+
102104
for (name, config) in project_config.ordered_contracts().iter() {
103105
let mut contract_path = root_path.clone();
104106
contract_path.push(&config.path);
@@ -109,33 +111,33 @@ mod sessions {
109111
.initial_contracts
110112
.push(repl::settings::InitialContract {
111113
code: code,
114+
path: contract_path.to_str().unwrap().into(),
112115
name: Some(name.clone()),
113116
deployer: deployer_address.clone(),
114117
});
115118
}
116119
settings.initial_deployer = initial_deployer;
117120
settings.include_boot_contracts = vec!["pox".to_string(), "costs".to_string(), "bns".to_string()];
118-
119121
let mut session = Session::new(settings.clone());
120122
session.start();
121123
session.advance_chain_tip(1);
122-
sessions.insert(session_id, session);
124+
sessions.insert(session_id, (name, session));
123125
Ok((session_id, settings.initial_accounts))
124126
}
125127

126-
pub fn perform_block<F, R>(session_id: u32, handler: F) -> Result<R, AnyError> where F: FnOnce(&mut Session) -> Result<R, AnyError> {
128+
pub fn perform_block<F, R>(session_id: u32, handler: F) -> Result<R, AnyError> where F: FnOnce(&str, &mut Session) -> Result<R, AnyError> {
127129
let mut sessions = SESSIONS.lock().unwrap();
128130
match sessions.get_mut(&session_id) {
129131
None => {
130132
println!("Error: unable to retrieve session");
131133
unreachable!()
132134
}
133-
Some(ref mut session) => handler(session),
135+
Some((name , ref mut session)) => handler(name.as_str(), session),
134136
}
135137
}
136138
}
137139

138-
pub async fn run_tests(files: Vec<String>) -> Result<(), AnyError> {
140+
pub async fn run_tests(files: Vec<String>, include_coverage: bool) -> Result<(), AnyError> {
139141

140142
let fail_fast = true;
141143
let quiet = false;
@@ -210,6 +212,24 @@ pub async fn run_tests(files: Vec<String>) -> Result<(), AnyError> {
210212
return Err(e);
211213
}
212214

215+
if include_coverage {
216+
let mut coverage_reporter = CoverageReporter::new();
217+
let sessions = sessions::SESSIONS.lock().unwrap();
218+
for (session_id, (name, session)) in sessions.iter() {
219+
220+
for contract in session.settings.initial_contracts.iter() {
221+
if let Some(ref name) = contract.name {
222+
if contract.path != "" {
223+
coverage_reporter.register_contract(name.clone(), contract.path.clone());
224+
}
225+
}
226+
}
227+
coverage_reporter.add_reports(&session.coverage_reports);
228+
coverage_reporter.add_asts(&session.asts);
229+
}
230+
231+
coverage_reporter.write_lcov_file("coverage.lcov");
232+
}
213233
Ok(())
214234
}
215235

@@ -368,11 +388,12 @@ where
368388
#[derive(Debug, Deserialize)]
369389
#[serde(rename_all = "camelCase")]
370390
struct SetupChainArgs {
391+
name: String,
371392
transactions: Vec<TransactionArgs>
372393
}
373394

374395
fn setup_chain(args: SetupChainArgs) -> Result<Value, AnyError> {
375-
let (session_id, accounts) = sessions::handle_setup_chain(args.transactions)?;
396+
let (session_id, accounts) = sessions::handle_setup_chain(args.name, args.transactions)?;
376397

377398
Ok(json!({
378399
"session_id": session_id,
@@ -419,7 +440,7 @@ struct TransferSTXArgs {
419440
}
420441

421442
fn mine_block(args: MineBlockArgs) -> Result<Value, AnyError> {
422-
let (block_height, receipts) = sessions::perform_block(args.session_id, |session| {
443+
let (block_height, receipts) = sessions::perform_block(args.session_id, |name, session| {
423444
let initial_tx_sender = session.get_tx_sender();
424445
let mut receipts = vec![];
425446
for tx in args.transactions.iter() {
@@ -433,12 +454,12 @@ fn mine_block(args: MineBlockArgs) -> Result<Value, AnyError> {
433454
} else {
434455
format!("(contract-call? '{}.{} {} {})", initial_tx_sender, args.contract, args.method, args.args.join(" "))
435456
};
436-
let execution = session.interpret(snippet, None, true).unwrap(); // todo(ludo)
457+
let execution = session.interpret(snippet, None, true, Some(name.into())).unwrap(); // todo(ludo)
437458
receipts.push((execution.result, execution.events));
438459
}
439460

440461
if let Some(ref args) = tx.deploy_contract {
441-
let execution = session.interpret(args.code.clone(), Some(args.name.clone()), true).unwrap(); // todo(ludo)
462+
let execution = session.interpret(args.code.clone(), Some(args.name.clone()), true, Some(name.into())).unwrap(); // todo(ludo)
442463
receipts.push((execution.result, execution.events));
443464
}
444465
}
@@ -466,7 +487,7 @@ struct MineEmptyBlocksArgs {
466487
}
467488

468489
fn mine_empty_blocks(args: MineEmptyBlocksArgs) -> Result<Value, AnyError> {
469-
let block_height = sessions::perform_block(args.session_id, |session| {
490+
let block_height = sessions::perform_block(args.session_id, |name, session| {
470491
let block_height = session.advance_chain_tip(args.count);
471492
Ok(block_height)
472493
})?;
@@ -488,7 +509,7 @@ struct CallReadOnlyFnArgs {
488509
}
489510

490511
fn call_read_only_fn(args: CallReadOnlyFnArgs) -> Result<Value, AnyError> {
491-
let (result, events) = sessions::perform_block(args.session_id, |session| {
512+
let (result, events) = sessions::perform_block(args.session_id, |name, session| {
492513
let initial_tx_sender = session.get_tx_sender();
493514
session.set_tx_sender(args.sender.clone());
494515

@@ -500,7 +521,7 @@ fn call_read_only_fn(args: CallReadOnlyFnArgs) -> Result<Value, AnyError> {
500521
format!("(contract-call? '{}.{} {} {})", initial_tx_sender, args.contract, args.method, args.args.join(" "))
501522
};
502523

503-
let execution = session.interpret(snippet, None, true).unwrap(); // todo(ludo)
524+
let execution = session.interpret(snippet, None, true, Some(name.into())).unwrap(); // todo(ludo)
504525
session.set_tx_sender(initial_tx_sender);
505526
Ok((execution.result, execution.events))
506527
})?;
@@ -519,7 +540,7 @@ struct GetAssetsMapsArgs {
519540
}
520541

521542
fn get_assets_maps(args: GetAssetsMapsArgs) -> Result<Value, AnyError> {
522-
let assets_maps = sessions::perform_block(args.session_id, |session| {
543+
let assets_maps = sessions::perform_block(args.session_id, |name, session| {
523544
let assets_maps = session.get_assets_maps();
524545
Ok(assets_maps)
525546
})?;

src/test/mod.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,13 @@ mod version;
4343

4444
mod deno;
4545

46-
pub fn run_tests(files: Vec<String>) {
47-
block_on(deno::run_tests(files));
46+
pub fn run_tests(files: Vec<String>, include_coverage: bool) {
47+
match block_on(deno::run_tests(files, include_coverage)) {
48+
Err(e) => {
49+
std::process::exit(1)
50+
}
51+
_ => {}
52+
};
4853
}
4954

5055
pub fn create_basic_runtime() -> tokio::runtime::Runtime {

0 commit comments

Comments
 (0)