001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.terminal.report; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import java.util.*; 022 023import org.apache.juneau.terminal.report.TerminalEvent.*; 024 025/** 026 * What a {@link ReportReader} recovered from a report. 027 * 028 * @param fw The framework key. 029 * @param tests The recovered tests, in report order. 030 * @param warnings Human-readable warnings, for example <c>report truncated: unexpected end of file after 4120 tests</c>. 031 * @param truncated Whether the file ended early or the test limit was hit. 032 * @param skippedFiles The number of files that were not read (over the size limit, unreadable). 033 */ 034public record ReportResult(String fw, List<ReportTest> tests, List<String> warnings, boolean truncated, int skippedFiles) { 035 036 private static final String UNNAMED = "(unnamed)"; 037 038 /** 039 * Constructor. 040 * 041 * @param fw The framework key. 042 * @param tests The recovered tests. 043 * @param warnings Human-readable warnings. 044 * @param truncated Whether the file ended early or the test limit was hit. 045 * @param skippedFiles The number of files that were not read. 046 */ 047 public ReportResult { 048 if (! TerminalViewChecks.isFw(fw)) 049 throw iaex("ReportResult fw must match ^[a-z0-9][a-z0-9._-]{0,31}$; got '%s'.", fw); 050 tests = List.copyOf(tests); 051 warnings = List.copyOf(warnings); 052 } 053 054 /** 055 * Returns the events a producer appends at the end of a step. 056 * 057 * <p> 058 * The sequence is a <c>replace</c> for the step, one <c>test</c> per recovered test, then one <c>warn</c> note per 059 * warning. When no test was recovered and there are warnings, only the notes are returned, so the step keeps its 060 * live <c>suite</c> placeholders. A valid but empty report yields just the <c>replace</c>. 061 * 062 * @param step The id of the step the results belong to. 063 * @return The events, not yet numbered. 064 */ 065 public List<TerminalEvent> toEvents(String step) { 066 var out = new ArrayList<TerminalEvent>(); 067 if (! (tests.isEmpty() && ! warnings.isEmpty())) 068 out.add(TerminalEvent.replace(step)); 069 out.addAll(tests.stream().map(t -> toEvent(step, t)).toList()); 070 out.addAll(warnings.stream().map(w -> TerminalEvent.note(Level.WARN, clip(w, TerminalEvent.MAX_NOTE)).withStep(step)).toList()); 071 return out; 072 } 073 074 private TerminalEvent toEvent(String step, ReportTest t) { 075 var e = TerminalEvent.test(step, fw, name(t.suite()), name(t.name()), t.status()); 076 if (t.ms() != null && t.ms() >= 0 && t.ms() <= TerminalEvent.MAX_SAFE_INT) 077 e = e.withMs(t.ms()); 078 var withMsg = t.msg() == null || t.msg().isEmpty() ? e : e.withMsg(clip(t.msg(), TerminalEvent.MAX_MSG)); 079 var full = t.trace() == null || t.trace().isEmpty() ? withMsg : withMsg.withTrace(clip(t.trace(), TerminalEvent.MAX_TRACE)); 080 // Control characters expand six-fold when serialized, so an extreme test can exceed the event size cap; shed the 081 // bulkiest members rather than lose the test. 082 for (var candidate : List.of(full, withMsg, e)) { 083 try { 084 return candidate.validate(); 085 } catch (IllegalArgumentException ex) { 086 // try the next, smaller candidate 087 } 088 } 089 return e; 090 } 091 092 private static String name(String s) { 093 return s == null || s.isEmpty() ? UNNAMED : clip(s, TerminalEvent.MAX_NAME); 094 } 095 096 private static String clip(String s, int max) { 097 return ReportText.clip(s, max); 098 } 099}