Skip to content

Context.prefix: opt-in class-name prefix for problematic tests so their compiled/*.java decompiled output is not overwritten by sibling tests #1020

@crowlogic

Description

@crowlogic

Proposal

Add Context.prefix — a String field, default "" — that the expression compiler prepends to every generated class name when non-empty.

Opt-in, per-test. A test that's hard to debug because the decompiled output in compiled/<className>.java gets overwritten by some other test compiling a class with the same name sets context.prefix = "myTest_" on its own Context before compiling expressions. Its decompiled artifacts land at compiled/myTest_F.java, compiled/myTest_operandF0001.java, compiled/myTest_a.java, etc., surviving any later test that compiles a plain F, operandF0001, or a.

Every other test runs unchanged. Production paths run unchanged. Nothing systematic. Used only when a specific test acts up.

Implementation

Context.java

Add the field next to the other simple public configuration fields (near saveClasses):

/**
 * Prefix prepended to every generated class name when this Context compiles
 * an Expression. Default empty string — no effect, behavior identical to
 * never having touched this field.
 *
 * <p>Used on a per-test basis when a test's decompiled output in
 * {@code compiled/<className>.java} keeps getting overwritten by another
 * test compiling a class with the same simple name. A problematic test
 * sets this on its own Context before compilation, and its generated
 * classes get unique file names that survive subsequent test runs.
 */
public String prefix = "";

Expression.java

In the one site that finalizes this.className before it gets used for ClassWriter, descriptor formatting, registration keys, and the ExpressionClassLoader's decompiled-source write path, prepend context.prefix:

this.className = (context != null ? context.prefix : "") + this.className;

Applied early enough that every downstream consumer of className sees the prefixed string. Sites that build "L" + className + ";" descriptors, sites that GETFIELD/PUTFIELD with className as the owner, the ExpressionClassLoader's findClass lookup, compiled/<className>.java file writing — all consume className after this point and don't need to know about the prefix.

Same prefix applied uniformly within one Context means every string that has to match another string within the test still matches.

Use

A problematic test:

@Test
public void testThatKeepsGettingItsDecompiledOutputOverwritten() {
  Context ctx = new Context();
  ctx.prefix = "fooTest_";
  // ... compile expressions against ctx, debug, inspect compiled/fooTest_*.java
}

That's it.

Ramifications

  1. Class-name strings are longer when prefix is set. Trace-log lines for that test get longer. Cosmetic.

  2. The prefix has to be a legal Java identifier prefix. No spaces, no slashes, no dots. The compiler does not validate this — if the user supplies garbage, the bytecode emit fails loudly at class-load time. Not worth defensive checking for a debug-only knob.

  3. Two tests setting the same prefix on different Contexts will collide. Same way two tests not setting any prefix collide today. Use a unique prefix per problematic test; that's the whole point.

  4. No effect on the function-mapping cache invariant (issue Decouple operand-class-name allocation from intermediate-variable field-name counter in NAryOperationNode #1019's tripwire). The prefix is applied uniformly to every class name within a single Context, so operandClassName and the parent's field-name string still share a counter-derived suffix and remain identity-equal as strings within the test. The cache continues to work as today.

  5. Production behavior unchanged. Field defaults to empty string, expression compiler prepends nothing, every code path is byte-identical to today when the field is left alone.

Out of scope

  • Systematic per-test prefixing across the entire suite. Not doing this. The capability exists for individual problematic tests.
  • JUnit TestWatcher integration, thread-locals, automatic derivation from test name. None of that. Test sets the field directly on its own Context.
  • Package-based isolation, decompiled-output subdirectories, FQN handling. None of that.

Files touched

  • src/main/java/arb/expressions/Context.java — one field, one javadoc block.
  • src/main/java/arb/expressions/Expression.java — one line at the className-finalization site.

Two lines of code change. No tests required — used only when a test needs it, and at that point the test itself exercises the capability.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions