Coder Social home page Coder Social logo

Comments (11)

MarvinJanssen avatar MarvinJanssen commented on August 24, 2024 1

Point is that people might not be aware of the order of operations and try to deploy a mock after calling mineBlock() by accident. That's why I like the readability of having a separate preSetup(). (Which we should arguably just call setup().) It also makes it easier to define a few setups that can be reused for different tests.

Changing mock behavior in the middle of a test? To me it screams for a separate test.

Indeed, which is why I wondered if there is a valid use-case at all.

If there would be a way to intercept contract-calls sent to a mock, pass them to JS that would calculate valid response that would be a game changer :)

Yep, I explored this but it leaves things to be desired. You'd need to do some interception and have it call back into a callback defined in test. Sounds tricky. It is why my MockContract sample generates all that is needed in JS, keeping Clarinet light. I think we can get away with setting a static return value from a mock call. I'm not sure if there are real use-cases for requiring calculation. (Do you have any examples?) The only thing we might want is a way to trigger a side-effect, like updating a map or data var in the mock contract to be used by a subsequent tx in the same test, or triggering a panic or event. But one can again wonder if setting static return values is enough. As any data to be returned from a public function can likewise be mocked.

from clarinet.

LNow avatar LNow commented on August 24, 2024

@MarvinJanssen can you give an example of things you would like to do in preSetup()?

from clarinet.

MarvinJanssen avatar MarvinJanssen commented on August 24, 2024

Deploy mock contracts or send initialising calls to contracts. Some of these things could be alleviated with deploy scripts but anything that is statically coded into the contract has to be present before Clarinet does its chain setup. Say that you are working on a prediction market that uses open-oracle, you'd want to mock that oracle in test.

from clarinet.

LNow avatar LNow commented on August 24, 2024

What if we would split setup_chain into two separate phases?

  1. setup_accounts
  2. setup_contracts

If it would be possible, we could start writing like tests like this:

Clarinet.test('Super important test', (chain: Chain, accounts: Accounts) => {
  let mockOracle = new MockContract("imaginary-oracle-v1", "SP000000000000000000002Q6VF78");
	mockOracle.publicFn("price",{
    inputs: [typeDefs.ascii(24), typeDefs.ascii(24), typeDefs.uint()],
    returns: types.ok(types.uint(2000000))
  });
  
  // we could also have a addSetupTx(tx: Tx) function
  chain.addMock(mockOracle);

  const wallet_1 = accounts.get("wallet_1")!;
  
  let block = chain.mineBlock([
    Tx.contractCall(`SP000000000000000000002Q6VF78.imaginary-oracle-v1`, "price",
    [
        types.ascii("usd"),
        types.ascii("stx"),
        types.uint(5)
    ], wallet_1.address),
  ]);

  console.info(block)
})

As long as we won't call mineBlock, mineEmptyBlock, mineEmptyBlockUntil, callReadOnlyFn, getAssetsMaps only accounts would be set up.
Once we call any of these functions, then all our contracts as well as mocks will be deployed and function we call will be executed.

I think this should give us a bit more pleasant so called developer experience.

Clarinet code would look then more or less like this:

export interface Accounts extends Map<string, Account> {}

export class Clarinet {
  public static test(name: string, fn: (chain:Chain, accounts: Accounts)=> void){  
    let chain = new Chain(name);
    let accounts = chain.accounts;

    const testDefinition: Deno.TestDefinition = {
      name: name,
      fn: async () => {
        await fn(chain, accounts);
      }
    }

    Deno.test(testDefinition);
  }
}

export class Chain {
  sessionId: number;
  accounts: Accounts = new Map();
  private isReady: boolean = false;
  private transactions: Array<Tx> = [];
  private name: string;

  constructor(name: string) {
    this.name = name;

    (Deno as any).core.ops();
    let result = (Deno as any).core.jsonOpSync("setup_account");

    this.sessionId = result["session_id"]

    for (let account of result["accounts"]) {
      this.accounts.set(account.name, account);
    }
  }

  private setUp() {
    let result = (Deno as any).core.jsonOpSync("setup_contracts", {
      name: this.name,
      transactions: this.transactions
    })
    // we reset accounts as their balances might be different after this phase
    this.accounts.clear();
    for (let account of result["accounts"]) {
      this.accounts.set(account.name, account);
    }

    this.isReady = true;
  }

  addMock(mock: MockContract):void {
    this.transactions.push(Tx.deployContract(mock));
  }

  mineBlock(transactions: Array<Tx>) {
    if(!this.isReady) {
      this.setUp();
    }

    let result = (Deno as any).core.jsonOpSync("mine_block", {
      sessionId: this.sessionId,
      transactions: transactions,
    });
    let block: Block = {
      height: result.block_height,
      receipts: result.receipts,
    };
    return block;
  }
....
....
}

from clarinet.

MarvinJanssen avatar MarvinJanssen commented on August 24, 2024

Interesting idea, although I wonder if this would be clear to people. I can see that people will try to deploy mocks after calling those functions.

If one can only deploy mocks during preSetup(), then there is a clear division. (Although I wonder if there would be a valid reason for being able to change the output / side-effects of a mock during the test() phase? If so, then then these could also be passed to test(), see #40. )

from clarinet.

LNow avatar LNow commented on August 24, 2024

The thing is that if your contract somehow depends on a mock, you can't deploy it after. It must be deployed before you can any function in you contract. Otherwise you won't be able to deploy it.

Deploying mock is nothing else but part of Arrange phase of your test.
With current test function signature you have something like this:

Clarinet.test({
  name: 'name of your test',
  preSetup(): Tx[] {
    //ARRANGE 
    // some setup transactions
  },
  async fn(chain: Chain, accounts: Map<string, Account>) {
    //ARRANGE - continuation
    
    //ACT

   //ASSERT
  }
});

And I think this one is a bit cleaner (only one arrange section):

Clarinet.test('name of your test', async (chain: Chain, accounts: Accounts) => {
   // ARRANGE

   // ACT

   // ASSERT
});

Changing mock behavior in the middle of a test? To me it screams for a separate test.
If there would be a way to intercept contract-calls sent to a mock, pass them to JS that would calculate valid response that would be a game changer :)
Baby steps - we'll get there.

from clarinet.

lgalabru avatar lgalabru commented on August 24, 2024

I'm trying to prioritize, is this feature request solving actual pain points? cc @MarvinJanssen @LNow

from clarinet.

lgalabru avatar lgalabru commented on August 24, 2024

I came up with a solution for this with #240, an example is described here: friedger/clarity-catamaranswaps#2.
Would love to get your thoughts on this approach @MarvinJanssen and @LNow!

from clarinet.

LNow avatar LNow commented on August 24, 2024

@lgalabru this look very nice.
Would it be possible to add "super advanced" version of it, let's say manualSetup in which users would have the possibility to manually control order of deployed contracts and block-height at which they are deployed?

manualSetup: async (chain: Chain, accounts: Map<string, Account>, contracts: Map<string, Contract>) => {
    const deployer = accounts.get("deployer")!;
   chain.mineEmptyBlock(400);

    chain.mineBlock([
      Tx.deployContract("cool-contract", contracts.get("cool-contract")!.code, deployer.address)
    ]);
 
    chain.mineBlock([
      Tx.contractCall("cool-contract", "init", [], deployer.address)
    ]);
    chain.mineEmptyBlock(10);

   chain.mineBlock([
      Tx.deployContract("another-contract", contracts.get("another-contract")!.code, deployer.address)
    ]);
  },

It would help simulate more realistic deployments.

from clarinet.

unclemantis avatar unclemantis commented on August 24, 2024

from clarinet.

lgalabru avatar lgalabru commented on August 24, 2024

Addressed in v0.28.0

from clarinet.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.