1 /*
2 * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23 package jdk.jfr.event.metadata;
24
25 import java.io.IOException;
26 import java.lang.reflect.Field;
27 import java.nio.file.Files;
28 import java.nio.file.Path;
29 import java.nio.file.Paths;
30 import java.util.Arrays;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.Set;
34 import java.util.stream.Collectors;
35 import java.util.stream.Stream;
36
37 import jdk.jfr.EventType;
38 import jdk.jfr.Experimental;
39 import jdk.jfr.FlightRecorder;
40 import jdk.test.lib.Utils;
41 import jdk.test.lib.jfr.EventNames;
42
43 /**
44 * @test Check for JFR events not covered by tests
45 * @requires vm.flagless
46 * @requires vm.hasJFR
47 * @library /test/lib /test/jdk
48 * @run main jdk.jfr.event.metadata.TestLookForUntestedEvents
49 */
50 public class TestLookForUntestedEvents {
51 private static final Path jfrTestRoot = Paths.get(Utils.TEST_SRC).getParent().getParent();
52 private static final String MSG_SEPARATOR = "==========================";
53 private static Set<String> jfrEventTypes = new HashSet<>();
54
55 private static final Set<String> hardToTestEvents = new HashSet<>(
56 Arrays.asList(
57 "DataLoss", "IntFlag", "ReservedStackActivation", "NativeLibraryUnload",
58 "DoubleFlag", "UnsignedLongFlagChanged", "IntFlagChanged",
59 "UnsignedIntFlag", "UnsignedIntFlagChanged", "DoubleFlagChanged")
60 );
61
62 // GC uses specific framework to test the events, instead of using event names literally.
63 // GC tests were inspected, as well as runtime output of GC tests.
64 // The following events below are know to be covered based on that inspection.
65 private static final Set<String> coveredGcEvents = new HashSet<>(
66 Arrays.asList(
67 "MetaspaceGCThreshold", "MetaspaceAllocationFailure", "MetaspaceOOM",
68 "MetaspaceChunkFreeListSummary", "G1HeapSummary", "ParallelOldGarbageCollection",
69 "OldGarbageCollection", "G1GarbageCollection", "GCPhasePause",
70 "GCPhasePauseLevel1", "GCPhasePauseLevel2", "GCPhasePauseLevel3",
71 "GCPhasePauseLevel4")
72 );
73
74 // Container events are tested in hotspot/jtreg/containers/docker/TestJFREvents.java
75 private static final Set<String> coveredContainerEvents = new HashSet<>(
76 Arrays.asList(
77 "ContainerConfiguration", "ContainerCPUUsage", "ContainerCPUThrottling",
78 "ContainerMemoryUsage", "ContainerIOUsage")
79 );
80 // These events are tested in test/jdk/java/lang/Thread/virtual/JfrEvents.java
81 private static final Set<String> coveredVirtualThreadEvents = Set.of(
82 "VirtualThreadPinned", "VirtualThreadSubmitFailed");
83
84 // This event is tested in test/jdk/java/lang/reflect/Field/mutateFinals/FinalFieldMutationEventTest.java
85 private static final Set<String> coveredFinalFieldMutationEvents = Set.of(
86 "FinalFieldMutationEvent");
87
88 // This is a "known failure list" for this test.
89 // NOTE: if the event is not covered, a bug should be open, and bug number
90 // noted in the comments for this set.
91 private static final Set<String> knownNotCoveredEvents = new HashSet<>(
92 );
93
94 // Experimental events
95 private static final Set<String> experimentalEvents = Set.of(
96 "Flush", "SyncOnValueBasedClass", "CPUTimeSample", "CPUTimeSamplesLost");
97
98 // Subset of the experimental events that should have tests
99 private static final Set<String> experimentalButTestedEvents = Set.of("CPUTimeSample");
100
101 public static void main(String[] args) throws Exception {
102 for (EventType type : FlightRecorder.getFlightRecorder().getEventTypes()) {
103 if (type.getAnnotation(Experimental.class) == null) {
104 jfrEventTypes.add(type.getName().replace("jdk.", ""));
105 }
106 }
107
108 checkEventNamesClass();
109 lookForEventsNotCoveredByTests();
110 }
111
112 // Look thru JFR tests to make sure JFR events are referenced in the tests
113 private static void lookForEventsNotCoveredByTests() throws Exception {
114 List<Path> paths = Files.walk(jfrTestRoot)
115 .filter(Files::isRegularFile)
116 .filter(path -> isJavaFile(path))
117 .collect(Collectors.toList());
118
119 Set<String> eventsNotCoveredByTest = new HashSet<>(jfrEventTypes);
120 Set<String> checkedEvents = new HashSet<>(jfrEventTypes);
121 checkedEvents.addAll(experimentalButTestedEvents);
122 for (String event : checkedEvents) {
123 for (Path p : paths) {
124 if (findStringInFile(p, event)) {
125 eventsNotCoveredByTest.remove(event);
126 break;
127 }
128 }
129 }
130
131 // Account for hard-to-test, experimental and GC tested events
132 eventsNotCoveredByTest.removeAll(hardToTestEvents);
133 eventsNotCoveredByTest.removeAll(coveredGcEvents);
134 eventsNotCoveredByTest.removeAll(coveredVirtualThreadEvents);
135 eventsNotCoveredByTest.removeAll(coveredFinalFieldMutationEvents);
136 eventsNotCoveredByTest.removeAll(coveredContainerEvents);
137 eventsNotCoveredByTest.removeAll(knownNotCoveredEvents);
138
139 if (!eventsNotCoveredByTest.isEmpty()) {
140 print(MSG_SEPARATOR + " Events not covered by test");
141 for (String event: eventsNotCoveredByTest) {
142 print(event);
143 }
144 print(MSG_SEPARATOR);
145 throw new RuntimeException("Found JFR events not covered by tests");
146 }
147 }
148
149 // Make sure all the JFR events are accounted for in jdk.test.lib.jfr.EventNames
150 private static void checkEventNamesClass() throws Exception {
151 // jdk.test.lib.jfr.EventNames
152 Set<String> eventsFromEventNamesClass = new HashSet<>();
153 for (Field f : EventNames.class.getFields()) {
154 String name = f.getName();
155 if (!name.equals("PREFIX")) {
156 String eventName = (String) f.get(null);
157 eventName = eventName.replace(EventNames.PREFIX, "");
158 eventsFromEventNamesClass.add(eventName);
159 }
160 }
161
162 // remove experimental events from eventsFromEventNamesClass since jfrEventTypes
163 // excludes experimental events
164 eventsFromEventNamesClass.removeAll(experimentalEvents);
165
166 if (!jfrEventTypes.equals(eventsFromEventNamesClass)) {
167 String exceptionMsg = "Events declared in jdk.test.lib.jfr.EventNames differ " +
168 "from events returned by FlightRecorder.getEventTypes()";
169 print(MSG_SEPARATOR);
170 print(exceptionMsg);
171 print("");
172 printSetDiff(jfrEventTypes, eventsFromEventNamesClass,
173 "jfrEventTypes", "eventsFromEventNamesClass");
174 print("");
175
176 print("This could be because:");
177 print("1) You forgot to write a unit test. Please do so in test/jdk/jdk/jfr/event/");
178 print("2) You wrote a unit test, but you didn't reference the event in");
179 print(" test/lib/jdk/test/lib/jfr/EventNames.java. ");
180 print("3) It is not feasible to test the event, not even a sanity test. ");
181 print(" Add the event name to test/lib/jdk/test/lib/jfr/EventNames.java ");
182 print(" and a short comment why it can't be tested");
183 print("4) The event is experimental. Please add 'experimental=\"true\"' to <Event> ");
184 print(" element in metadata.xml if it is a native event, or @Experimental if it is a ");
185 print(" Java event. The event will now not show up in JMC");
186 System.out.println(MSG_SEPARATOR);
187 throw new RuntimeException(exceptionMsg);
188 }
189 }
190
191 // ================ Helper methods
192 private static boolean isJavaFile(Path p) {
193 String fileName = p.getFileName().toString();
194 int i = fileName.lastIndexOf('.');
195 if ( (i < 0) || (i > fileName.length()) ) {
196 return false;
197 }
198 return "java".equals(fileName.substring(i+1));
199 }
200
201 private static boolean findStringInFile(Path p, String searchTerm) throws IOException {
202 long c = 0;
203 try (Stream<String> stream = Files.lines(p)) {
204 c = stream
205 .filter(line -> line.contains(searchTerm))
206 .count();
207 }
208 return (c != 0);
209 }
210
211 private static void printSetDiff(Set<String> a, Set<String> b,
212 String setAName, String setBName) {
213 if (a.size() > b.size()) {
214 a.removeAll(b);
215 System.out.printf("Set %s has more elements than set %s:", setAName, setBName);
216 System.out.println();
217 printSet(a);
218 } else {
219 b.removeAll(a);
220 System.out.printf("Set %s has more elements than set %s:", setBName, setAName);
221 System.out.println();
222 printSet(b);
223 }
224 }
225
226 private static void printSet(Set<String> set) {
227 for (String e : set) {
228 System.out.println(e);
229 }
230 }
231
232 private static void print(String s) {
233 System.out.println(s);
234 }
235 }