Skip to content

Latest commit

 

History

History
178 lines (151 loc) · 7.47 KB

architecture.md

File metadata and controls

178 lines (151 loc) · 7.47 KB

Partial Evaluation Architecture

How the ApprovalTests.Java.Minimizer tool is put together. For what it does and why, see PartialEvaluation.md.

All code lives in src/main/java/org/approvaltests/partial/.

Components

flowchart LR
    user(["Your project<br/>approvaltests-selection.json"])
    script["CreatePartailApprovals.sh<br/>compiles the tool, then runs it"]
    cli["PartialEvaluationCli<br/>build / explain, --current"]

    subgraph tool["ApprovalTests.Java.Minimizer"]
        config["SelectionConfig<br/>reads the JSON"]
        checkout["SourceCheckout<br/>clone, or fetch + reset"]
        jars["JarBuilder<br/>mvn package, merge jars,<br/>third-party classpath, stats"]
        shaker["TreeShaker<br/>mark + sweep on the source"]
        shake["ShakeReport<br/>what was removed,<br/>why each class was kept"]
        html["HtmlReport<br/>before/after HTML report"]
        shell["Shell<br/>runs git / mvn"]
    end

    github[("GitHub<br/>approvals/ApprovalTests.Java")]
    local[(".local/approvaltests-&lt;version&gt;<br/>or .local/approvaltests-master")]
    out(["target/approvaltests-custom/<br/>approvaltests-&lt;version&gt;.jar<br/>approvaltests-&lt;version&gt;-report.html"])

    user --> script --> cli
    cli --> config
    cli --> checkout
    cli --> jars
    cli --> shaker
    cli --> html
    shaker --> shake
    checkout --> shell
    jars --> shell
    shell --> github
    checkout --> local
    shaker --> local
    jars --> local
    jars --> out
    html --> out

Loading
Class Responsibility
SelectionDetector Scans a project's .java files with JavaParser + symbol solver, and collects the ApprovalTests methods, constructors, fields and types they use.
PartialEvaluationCli Parses build / explain / --current / --config, and runs the pipeline.
SelectionConfig The approvaltests-selection.json model (version, entry points, runtime discoveries, output).
SourceCheckout Gets the source: shallow-clone a tag or master, or fetch and reset --hard an existing checkout.
JarBuilder Runs Maven on approvaltests-util + approvaltests, writes their third-party classpath, merges the two jars into one, and measures jars.
TreeShaker Parses the source with JavaParser + symbol solver, marks what is reachable, and deletes the rest in place.
ShakeReport Results of a shake: deleted files and directories, removed methods and types, and the "kept because" chain for every kept class.
HtmlReport Compares the original source snapshot with the shaken checkout, and writes the HTML report.
Shell Runs git and mvn processes.

Creating the Selection File

sequenceDiagram
    autonumber
    participant S as CreateSelectionJson.sh
    participant CLI as PartialEvaluationCli detect
    participant M as mvn (your project)
    participant D as SelectionDetector
    participant P as Your project

    S->>CLI: detect --config approvaltests-selection.json
    CLI->>M: dependency:build-classpath (test scope)
    M-->>CLI: jars (approvaltests + third-party)
    CLI->>D: detect(project folder, classpath)
    D->>P: read every .java (skipping target, build, .git, .local, ...)
    D-->>CLI: version + entry points + unresolved
    CLI->>P: write approvaltests-selection.json (keeping runtimeDiscoveries and output)
Loading

Build Pipeline

sequenceDiagram
    autonumber
    participant CLI as PartialEvaluationCli
    participant SC as SourceCheckout
    participant JB as JarBuilder
    participant HR as HtmlReport
    participant TS as TreeShaker
    participant L as .local checkout
    participant P as Your project

    CLI->>SC: cloneTag(version) or cloneBranch("master") with --current
    SC->>L: clone, or fetch + checkout + reset --hard + clean
    CLI->>JB: build(checkout)
    JB->>L: mvn clean package dependency:build-classpath
    JB-->>CLI: original jars → baseline Stats
    CLI->>HR: snapshot(checkout)
    HR-->>CLI: original sources
    CLI->>TS: shake(checkout, entryPoints, thirdPartyJars)
    TS->>L: delete / rewrite .java files, delete empty directories
    TS-->>CLI: ShakeReport
    CLI->>JB: build(checkout) again
    JB->>L: mvn clean package (tests skipped)
    CLI->>JB: merge(jars)
    JB->>P: approvaltests-<version>.jar
    CLI->>HR: write(inputs)
    HR->>L: git diff (for the per-file diffs)
    HR->>P: approvaltests-<version>-report.html
Loading

explain runs steps 1–2, builds only if the third-party classpath is missing, shakes, and prints ShakeReport.keptBecause for classes matching the prefix. It does not build the partial jar.

Tree Shaking

flowchart TD
    start(["shake(checkout, entryPoints, thirdPartyJars)"])
    parse["Parse every .java in<br/>approvaltests-util and approvaltests<br/>(JavaParser + symbol solver with JDK,<br/>both source roots, third-party jars)"]
    index["Index types by name,<br/>methods by signature and by name"]
    roots["Mark entry points<br/>(methods, or the type for a field)"]

    subgraph mark["Mark (worklist until nothing new)"]
        drain["Scan each kept method / type"]
        refs["Type names → keep type<br/>(and its fields, initializers, constructors)"]
        strings["String literal = our class name → keep type"]
        calls["Method calls → resolve with symbol solver"]
        resolved{"Resolved?"}
        exact["Keep that method<br/>(all same-arity overloads if a lambda is passed)"]
        byname["Keep by name + arity,<br/>limited to the named class;<br/>nothing if the class isn't ours"]
        virtual["Virtual rules for kept types:<br/>abstract, @Override, implements inherited abstract,<br/>name called somewhere, third-party supertype"]
    end

    drop["Drop types kept only by a string<br/>that end up empty"]
    explain["Record 'kept because' chains"]

    subgraph sweep["Sweep"]
        files["Delete files with no kept types"]
        members["Delete unreached methods and nested types"]
        imports["Remove dead and unused imports"]
        write["Write files (formatting preserved)"]
        dirs["Delete empty directories"]
    end

    start --> parse --> index --> roots --> drain
    drain --> refs --> drain
    drain --> strings --> drain
    drain --> calls --> resolved
    resolved -- yes --> exact --> drain
    resolved -- no --> byname --> drain
    drain -- "worklist empty" --> virtual
    virtual -- "new methods kept" --> drain
    virtual -- "nothing new" --> drop --> explain --> files --> members --> imports --> write --> dirs
Loading

Where Things Live

flowchart LR
    subgraph repo["ApprovalTests.Java.Minimizer repo"]
        module["src, pom.xml,<br/>CreateSelectionJson.sh, CreatePartailApprovals.sh,<br/>build_and_test.sh"]
        dotlocal[".local/ (gitignored)<br/>approvaltests-31.0.0/<br/>approvaltests-master/"]
        spec["internal_documentation/<br/>PartialEvaluation.md, architecture.md"]
    end
    subgraph project["Your project"]
        selection["approvaltests-selection.json"]
        output["target/approvaltests-custom/<br/>jar + report"]
    end
    tmp[("java.io.tmpdir/approvaltests-partial-test/<br/>TreeShakerTest checkout")]

    module -- "runs from" --> selection
    module -- "checks out into" --> dotlocal
    module -- "writes" --> output
    module -. "tests use" .-> tmp
Loading
  • Checkouts: .local/ sits inside this repo so you can browse the shaken code.
  • Tests: TreeShakerTest clones the 31.0.0 tag into the temp directory, from a sibling ../ApprovalTests.Java if there is one (offline), and from GitHub otherwise.