Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Improvement]: Change internal implementation of ballerina function mocking to use function pointers #42281

Closed
Thushara-Piyasekara opened this issue Mar 6, 2024 · 0 comments
Labels
Area/TestFramework IceBox Reason/Other None of the other reasons. Team/DevTools Ballerina Developer Tooling ( CLI, Test FW, Package Management, OpenAPI, APIDocs ) Type/Improvement

Comments

@Thushara-Piyasekara
Copy link
Contributor

Thushara-Piyasekara commented Mar 6, 2024

Description

As of now function mocking in ballerina tests are done by dynamically loading the class of the mocked function and modifying the bytecode of the mocked function body using ASM. It is possible to do this more efficiently using function pointers.

Describe your problem(s)

Due to the usage of reflection to dynamically load the class for mocked function modification, the current implementation has a drawback when it comes to running tests through a GraalVM native image. Since GraalVM is an ahead of time compilation, we have to generate multiple JARs for each module with function mocking. This is a time consuming operation and the image generation time could multiply based on the number of modules with mocked functions.

Describe your solution(s)

It is possible to facilitate function mocking using function pointers instead of dynamic bytecode modification. With this implementation, GraalVM tests can be done using just one JAR. See the following example,

Before desugar phase

Imagine the following function is being mocked,

function foo() returns int {
    return 2;
}

The test suit will be as follows,

import ballerina/test;

@test:Mock {functionName: "foo"}
test:MockFunction mock_foo = new ();

@test:Config {}
function testReturn() {
    test:when(mock_foo).thenReturn(20);
    test:assertEquals(foo(), 20, msg = "function mocking failed");
}

After desugar phase

Mocking can be done by modifying the desugar phase in the ballerina compiler to desugar the mocked function(foo) to the following,

function () returns int fp = original_foo;

function foo() returns int {
    return fp();
}

function mock_foo() returns int {
    return 20;
}

function original_foo() returns int {
    return 2;
}

We can modify the testablePkg as well inside the desugar phase,

import ballerina/test;

@test:Config {}
function testReturn() {
    fp = mock_foo;
    test:assertEquals(foo(), 20, msg = "function mocking failed");
    fp = original_foo;
}

In the above code snippet we change the behavior of the foo function to mocked behavior in the fp = mock_foo; line. And restore it back to the original behavior in the fp = original_foo; line. This combination can be used for more complex mocking instances as well.

Related area

-> Test Framework

Related issue(s) (optional)

No response

Suggested label(s) (optional)

No response

Suggested assignee(s) (optional)

No response

@ballerina-bot ballerina-bot added Team/DevTools Ballerina Developer Tooling ( CLI, Test FW, Package Management, OpenAPI, APIDocs ) Area/TestFramework labels Mar 6, 2024
@Dilhasha Dilhasha added IceBox Reason/Other None of the other reasons. labels Sep 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area/TestFramework IceBox Reason/Other None of the other reasons. Team/DevTools Ballerina Developer Tooling ( CLI, Test FW, Package Management, OpenAPI, APIDocs ) Type/Improvement
Projects
None yet
Development

No branches or pull requests

3 participants