AGENTS.md 3.5 KB

AGENTS.md

Maven single-module demo (target Java 17) teaching ProcessBuilder/Process. No lint, no exec-maven-plugin. Runtime dep: logback-classic (slf4j); test dep: junit-jupiter (JUnit 5, via surefire). Code comments and tutorial notes (process.md, doc.md) are in Chinese.

Tests — mvn test (JUnit 5)

ProcessMechanicsTest drives the same fixtures end-to-end (compiles and runs child processes) and deliberately covers the stream-drain + timeout/destroy traps. Same user.dir rule applies — run from the repo root or the fixture .start() throws error=2. Tests recompile fixtures into src/main/resources/code/** (gitignored *.class). Keep tests fast: never block a full 10s sleep — use a short timeout then destroy().

Running examples

Classes are plain public static void main entry points in src/main/java/space/anyi/process/. There is no run target, so run manually after compiling:

mvn -q compile
java -cp "target/classes:$(mvn -q dependency:build-classpath -Dmdep.outputFile=/dev/stdout | tail -1)" space.anyi.process.<ClassName>

All examples declare static Logger log, so slf4j jars must be on the classpath or you get NoClassDefFoundError: org/slf4j/LoggerFactory (when manually invoking java -cp target/classes, not an env problem).

Child-process fixtures

  • Per-topic fixture dirs under src/main/resources/code/: hello/ (prints greeting), io/ (echoes stdin, writes stderr), executeStatus/ (sleeps 10s). Each demo's workDir points at exactly one fixture dir.
  • Demos invoke javac/java at runtime to compile and run fixture .java files; shared helpers live in space.anyi.process.Jdk (javac()/java() return absolute paths from java.home, compileFixture() runs javac). Spawned processes' PATH often lacks the JDK bin dir (error=2) — never go back to bare javac/java.
  • Demos hardcode workDir relative to user.dir → run them from the repo root, or ProcessBuilder.start() throws IOException: error=2. Keep workDir in sync with the fixture tree when moving files.
  • Also: space.anyi.process.Main in package root must NOT exist — Jdk.compileFixture uses -encoding UTF-8 + Main and fixtures are in the default package.

Working rules

  • Every example's spawned child process must run the Java program under src/main/resources/code/, never an ad-hoc script or system command. Keep the fixture tree in sync with each example's workDir.
  • Every code example must carry reasonable comments (Chinese, matching repo style) explaining what each step does and why.
  • Each case point (每个案例点) is demonstrated by exactly one dedicated static method, invoked from main() — do not fold several points into one linear main.
  • Commit to the local git repo after completing each example/point (git add + git commit); commit in small increments, one point per commit.

Known API traps in these examples

  • new BufferedReader(new InputStreamReader(p.getInputStream())).lines().forEach(...) blocks the calling thread until the child exits (stdout EOF only closes on exit). Draining stdout before waitFor(timeout) pins the main thread for the full child runtime (e.g. 10s) and silently defeats the timeout semantics. Drain child output on a background thread when using a timed waitFor.
  • Process.waitFor(timeout, unit) returns a boolean, not an exit code. After a timeout it returns false with the child still alive; calling process.exitValue() there throws IllegalThreadStateExceptiondestroy() + blocking waitFor() before reading exitValue().