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