Skip to content

Test generation fails for Optional<T> class in the plugin #226

Closed
@dtim

Description

@dtim

Description

No test cases are generated for methods involving Java Optional<T> class. UtErrors are generated by the engine during traverse due to ClassNotFound exception: UtOptional class can't be loaded. This behavior may be reproduced using the Idea plugin, but models are successfully generated for the same code in the engine unit tests.

To Reproduce

Generate unit tests for the sample method:

import java.util.Optional;

public class OptionalExamples {
    public Optional<Integer> nonEmptyIfPositive(int n) {
        if (n > 0) {
            return Optional.of(n);
        } else {
            return Optional.empty();
        }
    }
}

Expected behavior

Two tests are generated, one for each branch in the code (see Additional context for more details).

Actual behavior

Errors are reported:

public class OptionalExamplesTest {
    ///region Test suites for executable collections.OptionalExamples.nonEmptyIfPositive


    ///region Errors report for nonEmptyIfPositive

    public void testNonEmptyIfPositive_errors() {
        // Couldn't generate some tests. List of errors:
        // 
        // 2 occurrences of:
        // org.utbot.engine.overrides.collections.UtOptional

    }
    ///endregion

    ///endregion
}

Environment

Mockito: any configuration. Test framework: JUnit5.

Additional context

The primary error reason: UtOptional wrapper is used instead of Optional, but the class loader used by the test generator in the plugin can't find it. It's OK (engine-specific wrappers should never leak to the user), but shouldMock function checks the class and fails with ClassNotFound exception.

Adding a check to shouldMock fixes the exception, but the execution path corresponding to Optional.empty() return value is lost unless checkNpeForFinalFields setting is set to true (it is false by default). See SootField.shouldBeNotNull() declared in Extensions.kt.

A similar code sample that uses OptionalInt instead of Optional<Integer> is processed without any problems, because primitive field types are never mocked so no class check is performed.

Code sample:

public OptionalInt nonEmptyOptionalIntIfPositive(int n) {
    if (n > 0) {
        return OptionalInt.of(n);
    } else {
        return OptionalInt.empty();
    }
}

Generated tests:

///region SUCCESSFUL EXECUTIONS for method nonEmptyOptionalIntIfPositive(int)

/**
 * <pre>
 * Test executes conditions:
 *     {@code (n > 0): True }
 * invokes:
 *     OptionalInt::of once
 * returns from: {@code return OptionalInt.of(n); }
 * </pre>
 */
@Test
@DisplayName("nonEmptyOptionalIntIfPositive: n > 0 : True -> return OptionalInt.of(n)")
public void testNonEmptyOptionalIntIfPositive_NGreaterThanZero() {
    OptionalExamples optionalExamples = new OptionalExamples();

    OptionalInt actual = optionalExamples.nonEmptyOptionalIntIfPositive(1);

    OptionalInt expected = of(1);

    assertTrue(deepEquals(expected, actual));
}

/**
 * <pre>
 * Test executes conditions:
 *     {@code (n > 0): False }
 * </pre>
 */
@Test
@DisplayName("nonEmptyOptionalIntIfPositive: -> n > 0 : False")
public void testNonEmptyOptionalIntIfPositive_NLessOrEqualZero() throws ClassNotFoundException, Exception {
    Class optionalIntClazz = Class.forName("java.util.OptionalInt");
    OptionalInt prevEMPTY = ((OptionalInt) getStaticFieldValue(optionalIntClazz, "EMPTY"));
    try {
        OptionalInt empty = empty();
        setStaticField(optionalIntClazz, "EMPTY", empty);
        OptionalExamples optionalExamples = new OptionalExamples();

        OptionalInt actual = optionalExamples.nonEmptyOptionalIntIfPositive(0);


        assertTrue(deepEquals(empty, actual));
    } finally {
        setStaticField(OptionalInt.class, "EMPTY", prevEMPTY);
    }
}
///endregion

Metadata

Metadata

Assignees

Labels

comp-symbolic-engineIssue is related to the symbolic execution enginectg-bugIssue is a bugpriority-top-focusTop priority chosen by dev team

Type

No type

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions