1111package org .junit .platform .reporting .open .xml ;
1212
1313import static org .apiguardian .api .API .Status .EXPERIMENTAL ;
14+ import static org .junit .platform .commons .util .StringUtils .isNotBlank ;
1415import static org .junit .platform .reporting .open .xml .JUnitFactory .legacyReportingName ;
1516import static org .junit .platform .reporting .open .xml .JUnitFactory .type ;
1617import static org .junit .platform .reporting .open .xml .JUnitFactory .uniqueId ;
3031import static org .opentest4j .reporting .events .core .CoreFactory .tags ;
3132import static org .opentest4j .reporting .events .core .CoreFactory .uriSource ;
3233import static org .opentest4j .reporting .events .core .CoreFactory .userName ;
34+ import static org .opentest4j .reporting .events .git .GitFactory .branch ;
35+ import static org .opentest4j .reporting .events .git .GitFactory .commit ;
36+ import static org .opentest4j .reporting .events .git .GitFactory .repository ;
37+ import static org .opentest4j .reporting .events .git .GitFactory .status ;
3338import static org .opentest4j .reporting .events .java .JavaFactory .classSource ;
3439import static org .opentest4j .reporting .events .java .JavaFactory .classpathResourceSource ;
3540import static org .opentest4j .reporting .events .java .JavaFactory .fileEncoding ;
4247import static org .opentest4j .reporting .events .root .RootFactory .reported ;
4348import static org .opentest4j .reporting .events .root .RootFactory .started ;
4449
50+ import java .io .BufferedReader ;
4551import java .io .IOException ;
52+ import java .io .InputStream ;
53+ import java .io .InputStreamReader ;
54+ import java .io .Reader ;
4655import java .io .UncheckedIOException ;
4756import java .net .InetAddress ;
4857import java .net .UnknownHostException ;
58+ import java .nio .charset .Charset ;
4959import java .nio .file .Path ;
5060import java .time .Instant ;
5161import java .util .Map ;
62+ import java .util .Optional ;
5263import java .util .concurrent .ConcurrentHashMap ;
64+ import java .util .concurrent .TimeUnit ;
5365import java .util .concurrent .atomic .AtomicInteger ;
66+ import java .util .function .BiConsumer ;
5467
5568import org .apiguardian .api .API ;
5669import org .junit .platform .commons .JUnitException ;
70+ import org .junit .platform .commons .util .ExceptionUtils ;
5771import org .junit .platform .commons .util .StringUtils ;
5872import org .junit .platform .engine .ConfigurationParameters ;
5973import org .junit .platform .engine .TestExecutionResult ;
7488import org .junit .platform .launcher .listeners .OutputDir ;
7589import org .opentest4j .reporting .events .api .DocumentWriter ;
7690import org .opentest4j .reporting .events .api .NamespaceRegistry ;
91+ import org .opentest4j .reporting .events .core .Infrastructure ;
7792import org .opentest4j .reporting .events .core .Result ;
7893import org .opentest4j .reporting .events .core .Sources ;
7994import org .opentest4j .reporting .events .root .Events ;
@@ -103,6 +118,7 @@ public void testPlanExecutionStarted(TestPlan testPlan) {
103118 if (isEnabled (config )) {
104119 NamespaceRegistry namespaceRegistry = NamespaceRegistry .builder (Namespace .REPORTING_CORE ) //
105120 .add ("e" , Namespace .REPORTING_EVENTS ) //
121+ .add ("git" , Namespace .REPORTING_GIT ) //
106122 .add ("java" , Namespace .REPORTING_JAVA ) //
107123 .add ("junit" , JUnitFactory .NAMESPACE ,
108124 "https://junit.org/junit5/schemas/open-test-reporting/junit-1.9.xsd" ) //
@@ -138,9 +154,92 @@ private void reportInfrastructure() {
138154 .append (javaVersion (System .getProperty ("java.version" ))) //
139155 .append (fileEncoding (System .getProperty ("file.encoding" ))) //
140156 .append (heapSize (), heapSize -> heapSize .withMax (Runtime .getRuntime ().maxMemory ()));
157+
158+ addGitInfo (infrastructure );
141159 });
142160 }
143161
162+ private static void addGitInfo (Infrastructure infrastructure ) {
163+ boolean gitInstalled = exec ("git" , "--version" ).isPresent ();
164+ if (gitInstalled ) {
165+ exec ("git" , "config" , "--get" , "remote.origin.url" ) //
166+ .filter (StringUtils ::isNotBlank ) //
167+ .ifPresent (
168+ gitUrl -> infrastructure .append (repository (), repository -> repository .withOriginUrl (gitUrl )));
169+ exec ("git" , "rev-parse" , "--abbrev-ref" , "HEAD" ) //
170+ .filter (StringUtils ::isNotBlank ) //
171+ .ifPresent (branch -> infrastructure .append (branch (branch )));
172+ exec ("git" , "rev-parse" , "--verify" , "HEAD" ) //
173+ .filter (StringUtils ::isNotBlank ) //
174+ .ifPresent (gitCommitHash -> infrastructure .append (commit (gitCommitHash )));
175+ exec ("git" , "status" , "--porcelain" ) //
176+ .ifPresent (statusOutput -> infrastructure .append (status (statusOutput ),
177+ status -> status .withClean (statusOutput .isEmpty ())));
178+ }
179+ }
180+
181+ static Optional <String > exec (String ... args ) {
182+
183+ Process process = startProcess (args );
184+
185+ try (Reader out = newBufferedReader (process .getInputStream ());
186+ Reader err = newBufferedReader (process .getErrorStream ())) {
187+
188+ StringBuilder output = new StringBuilder ();
189+ readAllChars (out , (chars , numChars ) -> output .append (chars , 0 , numChars ));
190+
191+ readAllChars (err , (__ , ___ ) -> {
192+ // ignore
193+ });
194+
195+ boolean terminated = process .waitFor (10 , TimeUnit .SECONDS );
196+ return terminated && process .exitValue () == 0 ? Optional .of (trimAtEnd (output )) : Optional .empty ();
197+ }
198+ catch (InterruptedException e ) {
199+ throw ExceptionUtils .throwAsUncheckedException (e );
200+ }
201+ catch (IOException ignore ) {
202+ return Optional .empty ();
203+ }
204+ finally {
205+ process .destroyForcibly ();
206+ }
207+ }
208+
209+ private static BufferedReader newBufferedReader (InputStream stream ) {
210+ return new BufferedReader (new InputStreamReader (stream , Charset .defaultCharset ()));
211+ }
212+
213+ private static Process startProcess (String [] args ) {
214+ Process process ;
215+ try {
216+ process = Runtime .getRuntime ().exec (args );
217+ }
218+ catch (IOException e ) {
219+ throw new RuntimeException (e );
220+ }
221+ return process ;
222+ }
223+
224+ private static void readAllChars (Reader reader , BiConsumer <char [], Integer > consumer ) throws IOException {
225+ char [] buffer = new char [1024 ];
226+ int numChars ;
227+ while ((numChars = reader .read (buffer )) != -1 ) {
228+ consumer .accept (buffer , numChars );
229+ }
230+ }
231+
232+ private static String trimAtEnd (StringBuilder value ) {
233+ int endIndex = value .length ();
234+ for (int i = value .length () - 1 ; i >= 0 ; i --) {
235+ if (Character .isWhitespace (value .charAt (i ))) {
236+ endIndex --;
237+ break ;
238+ }
239+ }
240+ return value .substring (0 , endIndex );
241+ }
242+
144243 @ Override
145244 public void testPlanExecutionFinished (TestPlan testPlan ) {
146245 try {
@@ -160,7 +259,7 @@ public void executionSkipped(TestIdentifier testIdentifier, String reason) {
160259 reportStarted (testIdentifier , id );
161260 eventsFileWriter .append (finished (id , Instant .now ()), //
162261 finished -> finished .append (result (Result .Status .SKIPPED ), result -> {
163- if (StringUtils . isNotBlank (reason )) {
262+ if (isNotBlank (reason )) {
164263 result .append (reason (reason ));
165264 }
166265 }));
0 commit comments