clarinet icon indicating copy to clipboard operation
clarinet copied to clipboard

Support Deno "import map" in tests

Open kantai opened this issue 3 years ago • 3 comments

Some libraries in denoland require an import map to be specified. For example, secp256k1.

I patched my local instance of Clarinet to support this via my manifest file:

diff --git a/components/clarinet-cli/src/runner/deno.rs b/components/clarinet-cli/src/runner/deno.rs
index c8b5c7d..9a5ae8a 100644
--- a/components/clarinet-cli/src/runner/deno.rs
+++ b/components/clarinet-cli/src/runner/deno.rs
@@ -65,6 +65,7 @@ pub async fn do_run_scripts(
     let mut flags = Flags::default();
     flags.unstable = true;
     flags.reload = true;
+    flags.import_map_path = manifest.project.deno_import_file.clone();
     if allow_disk_write {
         let mut write_path_location = manifest.location.get_project_root_location().unwrap();
         write_path_location.append_path("artifacts");
diff --git a/components/clarinet-files/src/project_manifest.rs b/components/clarinet-files/src/project_manifest.rs
index 4353df6..8338e2e 100644
--- a/components/clarinet-files/src/project_manifest.rs
+++ b/components/clarinet-files/src/project_manifest.rs
@@ -18,7 +18,7 @@ pub struct ProjectConfigFile {
     telemetry: Option<bool>,
     requirements: Option<Value>,
     boot_contracts: Option<Vec<String>>,
-
+    deno_import_file: Option<String>,
     // The fields below have been moved into repl above, but are kept here for
     // backwards compatibility.
     analysis: Option<Vec<clarity_repl::analysis::Pass>>,
@@ -47,6 +47,7 @@ pub struct ProjectConfig {
     pub requirements: Option<Vec<RequirementConfig>>,
     pub cache_location: FileLocation,
     pub boot_contracts: Vec<String>,
+    pub deno_import_file: Option<String>,
 }
 
 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
@@ -124,6 +125,7 @@ impl ProjectManifest {
                 format!("costs-v{}", repl_settings.costs_version),
                 "bns".to_string(),
             ]),
+            deno_import_file: project_manifest_file.project.deno_import_file,
         };
 
         let mut config = ProjectManifest {

It's a pretty simple patch, but I think I ran into some other issues related either to TSC versions or the deno vendoring:

In sample_test.ts:

import * as secp from "https://deno.land/x/secp256k1/mod.ts";
$ clarinet test 
TS2339 [ERROR]: Property 'cause' does not exist on type 'PartialReadError'.
          e.cause = err.cause;
            ~~~~~
    at https://deno.land/[email protected]/io/buffer.ts:432:13

TS2339 [ERROR]: Property 'cause' does not exist on type 'Error'.
          e.cause = err.cause;
                        ~~~~~
    at https://deno.land/[email protected]/io/buffer.ts:432:25

TS2339 [ERROR]: Property 'cause' does not exist on type 'PartialReadError'.
          e.cause = err.cause;
            ~~~~~
    at https://deno.land/[email protected]/io/buffer.ts:618:13

TS2339 [ERROR]: Property 'cause' does not exist on type 'Error'.
          e.cause = err.cause;
                        ~~~~~
    at https://deno.land/[email protected]/io/buffer.ts:618:25

TS2339 [ERROR]: Property 'cause' does not exist on type 'PartialReadError'.
          e.cause = err.cause;
            ~~~~~
    at https://deno.land/[email protected]/io/buffer.ts:663:13

TS2339 [ERROR]: Property 'cause' does not exist on type 'Error'.
          e.cause = err.cause;
                        ~~~~~
    at https://deno.land/[email protected]/io/buffer.ts:663:25

TS2345 [ERROR]: Argument of type 'Hex' is not assignable to parameter of type 'Uint8Array'.
  Type 'string' is not assignable to type 'Uint8Array'.
    const str = arr ? bytesToHex(hex) : hex;
                                 ~~~
    at https://deno.land/x/[email protected]/index.ts:598:34

TS2345 [ERROR]: Argument of type 'string | Uint8Array' is not assignable to parameter of type 'string'.
  Type 'Uint8Array' is not assignable to type 'string'.
    return new Signature(hexToNumber(str.slice(0, 64)), hexToNumber(str.slice(64, 128)));
                                     ~~~~~~~~~~~~~~~~
    at https://deno.land/x/[email protected]/index.ts:600:38

TS2345 [ERROR]: Argument of type 'string | Uint8Array' is not assignable to parameter of type 'string'.
  Type 'Uint8Array' is not assignable to type 'string'.
    return new Signature(hexToNumber(str.slice(0, 64)), hexToNumber(str.slice(64, 128)));
                                                                    ~~~~~~~~~~~~~~~~~~
    at https://deno.land/x/[email protected]/index.ts:600:69

TS2345 [ERROR]: Argument of type 'Hex' is not assignable to parameter of type 'Uint8Array'.
  Type 'string' is not assignable to type 'Uint8Array'.
    const { r, s } = parseDERSignature(arr ? hex : hexToBytes(hex));
                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~
    at https://deno.land/x/[email protected]/index.ts:609:40

TS2345 [ERROR]: Argument of type 'Hex' is not assignable to parameter of type 'string'.
  Type 'Uint8Array' is not assignable to type 'string'.
    const { r, s } = parseDERSignature(arr ? hex : hexToBytes(hex));
                                                              ~~~
    at https://deno.land/x/[email protected]/index.ts:609:63

TS2322 [ERROR]: Type 'Hex | SchnorrSignature' is not assignable to type 'SchnorrSignature'.
  Type 'string' is not assignable to type 'SchnorrSignature'.
  const sig: SchnorrSignature = raw ? signature : SchnorrSignature.fromHex(signature);
        ~~~
    at https://deno.land/x/[email protected]/index.ts:1377:9

Found 12 errors.

Whereas the deno cli (v1.23) is perfectly happy with that file:

deno run --import-map=import_map.json ./sample_test.ts

kantai avatar Aug 05 '22 15:08 kantai

Thanks Aaron! This issue will be be addressed in https://github.com/hirosystems/clarinet/pull/511. It looks functional so this is blocker on your end, you can hop into this branch, install a copy and use

clarinet test --import-map=imports.json

lgalabru avatar Aug 07 '22 23:08 lgalabru

I will try this out in my branch. I used a workaround to get unblocked (basically just hardcoding signature values), but I'll see if this works.

What are your thoughts on configuring the --import-map via the manifest rather than having to pass a command line option? Other settings in Clarinet don't need to be passed through the CLI.

kantai avatar Aug 09 '22 14:08 kantai

Yep, it worked!

$ clarinet test
./tests/hyperchains/hyperchains_test.ts => Unit test the withdrawal leaf hash calculations using test vectors ... ok (21ms)
./tests/hyperchains/hyperchains_test.ts => Ensure that block can be committed by subnet miner ... ok (29ms)
./tests/hyperchains/hyperchains_test.ts => Ensure that user can register and setup assets  ... ok (26ms)
./tests/hyperchains/hyperchains_test.ts => Ensure that user can deposit NFT & miner can withdraw it ... ok (36ms)
./tests/hyperchains/hyperchains_test.ts => Ensure that user can deposit FT & miner can withdraw it ... ok (43ms)
./tests/hyperchains/hyperchains_test.ts => Ensure that user can withdraw FT minted on hyperchain & L1 miner can mint it ... ok (32ms)
./tests/hyperchains/hyperchains_test.ts => Ensure that withdrawals work with a more complex Merkle tree ... ok (57ms)
./tests/hyperchains/hyperchains_test.ts => Ensure that L1 contract can't mint an NFT first created on the hyperchain if it already exists on the L1 ... ok (20ms)
./tests/hyperchains/hyperchains_test.ts => Ensure that user can mint an NFT on the hyperchain and L1 miner can withdraw it by minting ... ok (24ms)
./tests/hyperchains/hyperchains_test.ts => Ensure that a miner can't withdraw an NFT if nobody owns it, in the `no-mint` case. ... ok (19ms)
./tests/hyperchains/hyperchains_test.ts => Ensure that a miner can withdraw an NFT to the original owner, in the `no-mint` case. ... ok (24ms)
./tests/hyperchains/hyperchains_test.ts => Ensure that a miner can withdraw an NFT to a different user, in the `no-mint` case. ... ok (24ms)

ok | 12 passed | 0 failed (381ms)

error:: Relative import path "crypto" not prefixed with / or ./ or ../
    at https://deno.land/x/[email protected]/index.ts:6:29

$ clarinet test --import-map=./import_map.json --allow-net
./tests/hyperchains/hyperchains_test.ts => Unit test the withdrawal leaf hash calculations using test vectors ... ok (13ms)
./tests/hyperchains/hyperchains_test.ts => Ensure that block can be committed by subnet miner ... ok (24ms)
./tests/hyperchains/hyperchains_test.ts => Ensure that user can register and setup assets  ... ok (26ms)
./tests/hyperchains/multiminer_test.ts => Test multi-party commit when one party submits transactions and other party is signatory ... ok (66ms)
./tests/hyperchains/hyperchains_test.ts => Ensure that user can deposit NFT & miner can withdraw it ... ok (37ms)
./tests/hyperchains/multiminer_test.ts => Test multi-party commit when 2 parties are signatories ... ok (49ms)
./tests/hyperchains/hyperchains_test.ts => Ensure that user can deposit FT & miner can withdraw it ... ok (46ms)
./tests/hyperchains/hyperchains_test.ts => Ensure that user can withdraw FT minted on hyperchain & L1 miner can mint it ... ok (32ms)
./tests/hyperchains/hyperchains_test.ts => Ensure that withdrawals work with a more complex Merkle tree ... ok (56ms)
./tests/hyperchains/hyperchains_test.ts => Ensure that L1 contract can't mint an NFT first created on the hyperchain if it already exists on the L1 ... ok (21ms)
./tests/hyperchains/hyperchains_test.ts => Ensure that user can mint an NFT on the hyperchain and L1 miner can withdraw it by minting ... ok (24ms)
./tests/hyperchains/hyperchains_test.ts => Ensure that a miner can't withdraw an NFT if nobody owns it, in the `no-mint` case. ... ok (18ms)
./tests/hyperchains/hyperchains_test.ts => Ensure that a miner can withdraw an NFT to the original owner, in the `no-mint` case. ... ok (25ms)
./tests/hyperchains/hyperchains_test.ts => Ensure that a miner can withdraw an NFT to a different user, in the `no-mint` case. ... ok (25ms)

ok | 14 passed | 0 failed (367ms)

Note that the --allow-net CLI option was necessary to avoid a prompt from deno:

$ clarinet test --import-map=./import_map.json 
⚠️  ️Deno requests net access to "deno.land". Run again with --allow-net to bypass this prompt.
   Allow? [y/n (y = yes allow, n = no deny)]  n

It worked without the net access, but to avoid the prompt, I had to add --allow-net. I'm not sure if you want to capture those prompts in Clarinet so that it doesn't break, e.g., CI integrations.

kantai avatar Aug 09 '22 15:08 kantai