1 /*
   2  * Copyright (c) 2013, 2015, 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 
  24 
  25 package org.graalvm.compiler.debug.test;
  26 
  27 import static org.graalvm.compiler.debug.DebugContext.NO_DESCRIPTION;
  28 import static org.graalvm.compiler.debug.DebugContext.NO_GLOBAL_METRIC_VALUES;
  29 
  30 import java.io.BufferedReader;
  31 import java.io.ByteArrayOutputStream;
  32 import java.io.IOException;
  33 import java.io.InputStreamReader;
  34 import java.io.PrintStream;
  35 import java.util.Arrays;
  36 import java.util.Collections;
  37 import java.util.Formatter;
  38 import java.util.List;
  39 import java.util.stream.Collectors;
  40 
  41 import jdk.internal.vm.compiler.collections.EconomicMap;
  42 import org.graalvm.compiler.debug.Assertions;
  43 import org.graalvm.compiler.debug.CounterKey;
  44 import org.graalvm.compiler.debug.DebugCloseable;
  45 import org.graalvm.compiler.debug.DebugContext;
  46 import org.graalvm.compiler.debug.DebugContext.Scope;
  47 import org.graalvm.compiler.debug.DebugDumpHandler;
  48 import org.graalvm.compiler.debug.DebugHandler;
  49 import org.graalvm.compiler.debug.DebugHandlersFactory;
  50 import org.graalvm.compiler.debug.DebugOptions;
  51 import org.graalvm.compiler.debug.DebugVerifyHandler;
  52 import org.graalvm.compiler.options.OptionKey;
  53 import org.graalvm.compiler.options.OptionValues;
  54 import org.junit.Assert;
  55 import org.junit.Assume;
  56 import org.junit.Test;
  57 
  58 @SuppressWarnings("try")
  59 public class DebugContextTest {
  60 
  61     static class DebugContextSetup {
  62         final Formatter dumpOutput = new Formatter();
  63         final Formatter verifyOutput = new Formatter();
  64         final ByteArrayOutputStream logOutput = new ByteArrayOutputStream();
  65         DebugHandlersFactory handlers = new DebugHandlersFactory() {
  66             @Override
  67             public List<DebugHandler> createHandlers(OptionValues options) {
  68                 return Arrays.asList(new DebugDumpHandler() {
  69                     @Override
  70                     public void dump(DebugContext ignore, Object object, String format, Object... arguments) {
  71                         dumpOutput.format("Dumping %s with label \"%s\"%n", object, String.format(format, arguments));
  72                     }
  73                 }, new DebugVerifyHandler() {
  74                     @Override
  75                     public void verify(DebugContext ignore, Object object, String format, Object... args) {
  76                         verifyOutput.format("Verifying %s with label \"%s\"%n", object, String.format(format, args));
  77                     }
  78                 });
  79             }
  80         };
  81 
  82         DebugContext openDebugContext(OptionValues options) {
  83             return DebugContext.create(options, NO_DESCRIPTION, NO_GLOBAL_METRIC_VALUES, new PrintStream(logOutput), Collections.singletonList(handlers));
  84 
  85         }
  86     }
  87 
  88     @Test
  89     public void testDisabledScopes() {
  90         OptionValues options = new OptionValues(EconomicMap.create());
  91         DebugContextSetup setup = new DebugContextSetup();
  92         try (DebugContext debug = setup.openDebugContext(options);
  93                         DebugContext.Scope d = debug.scope("TestDisabledScoping")) {
  94             for (int level = DebugContext.BASIC_LEVEL; level <= DebugContext.VERY_DETAILED_LEVEL; level++) {
  95                 debug.dump(level, "an object", "at level %d", level);
  96                 debug.verify("an object", "at level %d", level);
  97                 debug.log(level, "log statement at level %d", level);
  98             }
  99         }
 100         String log = setup.logOutput.toString();
 101         String dumpOutput = setup.dumpOutput.toString();
 102         String verifyOutput = setup.verifyOutput.toString();
 103         Assert.assertTrue(log, log.isEmpty());
 104         Assert.assertTrue(dumpOutput, dumpOutput.isEmpty());
 105         Assert.assertTrue(verifyOutput, verifyOutput.isEmpty());
 106     }
 107 
 108     @Test
 109     public void testDumping() {
 110         for (int level = DebugContext.BASIC_LEVEL; level <= DebugContext.VERY_DETAILED_LEVEL; level++) {
 111             OptionValues options = new OptionValues(EconomicMap.create());
 112             options = new OptionValues(options, DebugOptions.Dump, "Scope" + level + ":" + level);
 113             DebugContextSetup setup = new DebugContextSetup();
 114             try (DebugContext debug = setup.openDebugContext(options);
 115                             DebugContext.Scope s0 = debug.scope("TestDumping")) {
 116                 try (DebugContext.Scope s1 = debug.scope("Scope1")) {
 117                     try (DebugContext.Scope s2 = debug.scope("Scope2")) {
 118                         try (DebugContext.Scope s3 = debug.scope("Scope3")) {
 119                             try (DebugContext.Scope s4 = debug.scope("Scope4")) {
 120                                 try (DebugContext.Scope s5 = debug.scope("Scope5")) {
 121                                     debug.dump(level, "an object", "at level %d", level);
 122                                 }
 123                             }
 124                         }
 125                     }
 126                 }
 127 
 128             }
 129 
 130             String expect = String.format("Dumping an object with label \"at level %d\"%n", level);
 131             String dump = setup.dumpOutput.toString();
 132             Assert.assertEquals(expect, dump);
 133         }
 134     }
 135 
 136     @Test
 137     public void testLogging() throws IOException {
 138         OptionValues options = new OptionValues(EconomicMap.create());
 139         options = new OptionValues(options, DebugOptions.Log, ":5");
 140         DebugContextSetup setup = new DebugContextSetup();
 141         try (DebugContext debug = setup.openDebugContext(options)) {
 142             for (int level = DebugContext.BASIC_LEVEL; level <= DebugContext.VERY_DETAILED_LEVEL; level++) {
 143                 try (DebugContext.Scope s0 = debug.scope("TestLogging")) {
 144                     debug.log(level, "log statement at level %d", level);
 145                     try (DebugContext.Scope s1 = debug.scope("Level1")) {
 146                         debug.log(level, "log statement at level %d", level);
 147                         try (DebugContext.Scope s2 = debug.scope("Level2")) {
 148                             debug.log(level, "log statement at level %d", level);
 149                             try (DebugContext.Scope s3 = debug.scope("Level3")) {
 150                                 debug.log(level, "log statement at level %d", level);
 151                                 try (DebugContext.Scope s4 = debug.scope("Level4")) {
 152                                     debug.log(level, "log statement at level %d", level);
 153                                     try (DebugContext.Scope s5 = debug.scope("Level5")) {
 154                                         debug.log(level, "log statement at level %d", level);
 155                                     }
 156                                 }
 157                             }
 158                         }
 159                     }
 160                 }
 161             }
 162         }
 163         String expected;
 164         try (BufferedReader input = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream(getClass().getSimpleName() + ".testLogging.input")))) {
 165             String threadLabel = "[thread:" + Thread.currentThread().getId() + "]";
 166             expected = input.lines().collect(Collectors.joining(System.lineSeparator(), "", System.lineSeparator())).replace("[thread:1]", threadLabel);
 167         }
 168         String logged = setup.logOutput.toString();
 169         Assert.assertEquals(expected, logged);
 170     }
 171 
 172     @Test
 173     public void testContextScope() {
 174         OptionValues options = new OptionValues(EconomicMap.create());
 175         options = new OptionValues(options, DebugOptions.Log, ":5");
 176         DebugContextSetup setup = new DebugContextSetup();
 177         try (DebugContext debug = setup.openDebugContext(options)) {
 178             try (DebugContext.Scope s0 = debug.scope("TestLogging")) {
 179                 try (DebugContext.Scope s1 = debug.withContext("A")) {
 180                     for (Object o : debug.context()) {
 181                         Assert.assertEquals(o, "A");
 182                     }
 183                 } catch (Throwable t) {
 184                     throw debug.handle(t);
 185                 }
 186                 try (DebugContext.Scope s1 = debug.withContext("B")) {
 187                     for (Object o : debug.context()) {
 188                         Assert.assertEquals(o, "B");
 189                     }
 190                 } catch (Throwable t) {
 191                     throw debug.handle(t);
 192                 }
 193             }
 194         }
 195 
 196     }
 197 
 198     @Test
 199     public void testEnabledSandbox() {
 200         TimerKeyTest.assumeManagementLibraryIsLoadable();
 201         EconomicMap<OptionKey<?>, Object> map = EconomicMap.create();
 202         // Configure with an option that enables scopes
 203         map.put(DebugOptions.DumpOnError, true);
 204         OptionValues options = new OptionValues(map);
 205         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 206         DebugContext debug = DebugContext.create(options, NO_DESCRIPTION, NO_GLOBAL_METRIC_VALUES, new PrintStream(baos), DebugHandlersFactory.LOADER);
 207         Exception e = new Exception("testEnabledSandbox");
 208         String scopeName = "";
 209         try {
 210             try (DebugContext.Scope d = debug.sandbox("TestExceptionHandling", debug.getConfig())) {
 211                 scopeName = d.getQualifiedName();
 212                 throw e;
 213             } catch (Throwable t) {
 214                 assert e == t;
 215                 debug.handle(t);
 216             }
 217         } catch (Throwable t) {
 218             // The exception object should propagate all the way out through
 219             // a enabled sandbox scope
 220             Assert.assertEquals(e, t);
 221         }
 222         String logged = baos.toString();
 223         String expected = String.format("Exception raised in scope %s: %s", scopeName, e);
 224         String line = "-------------------------------------------------------";
 225         Assert.assertTrue(String.format("Could not find \"%s\" in content between lines below:%n%s%n%s%s", expected, line, logged, line), logged.contains(expected));
 226     }
 227 
 228     @Test
 229     public void testDisabledSandbox() {
 230         TimerKeyTest.assumeManagementLibraryIsLoadable();
 231         EconomicMap<OptionKey<?>, Object> map = EconomicMap.create();
 232         // Configure with an option that enables scopes
 233         map.put(DebugOptions.DumpOnError, true);
 234         OptionValues options = new OptionValues(map);
 235         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 236         DebugContext debug = DebugContext.create(options, NO_DESCRIPTION, NO_GLOBAL_METRIC_VALUES, new PrintStream(baos), DebugHandlersFactory.LOADER);
 237         Exception e = new Exception("testDisabledSandbox");
 238         try {
 239             // Test a disabled sandbox scope
 240             try (DebugContext.Scope d = debug.sandbox("TestExceptionHandling", null)) {
 241                 throw e;
 242             } catch (Throwable t) {
 243                 assert e == t;
 244                 debug.handle(t);
 245             }
 246         } catch (Throwable t) {
 247             // The exception object should propagate all the way out through
 248             // a disabled sandbox scope
 249             Assert.assertEquals(e, t);
 250         }
 251         String logged = baos.toString();
 252         Assert.assertTrue(logged, logged.isEmpty());
 253     }
 254 
 255     /**
 256      * Tests that using a {@link DebugContext} on a thread other than the one on which it was
 257      * created causes an assertion failure.
 258      */
 259     @Test
 260     public void testInvariantChecking() throws InterruptedException {
 261         Assume.assumeTrue(Assertions.assertionsEnabled());
 262         EconomicMap<OptionKey<?>, Object> map = EconomicMap.create();
 263         // Configure with an option that enables counters
 264         map.put(DebugOptions.Counters, "");
 265         OptionValues options = new OptionValues(map);
 266         DebugContext debug = DebugContext.create(options, DebugHandlersFactory.LOADER);
 267         CounterKey counter = DebugContext.counter("DebugContextTestCounter");
 268         AssertionError[] result = {null};
 269         Thread thread = new Thread() {
 270 
 271             @Override
 272             public void run() {
 273                 try {
 274                     counter.add(debug, 1);
 275                 } catch (AssertionError e) {
 276                     result[0] = e;
 277                 }
 278             }
 279         };
 280         thread.start();
 281         thread.join();
 282 
 283         Assert.assertNotNull("Expected thread to throw AssertionError", result[0]);
 284     }
 285 
 286     @Test
 287     public void testDisableIntercept() {
 288         TimerKeyTest.assumeManagementLibraryIsLoadable();
 289         EconomicMap<OptionKey<?>, Object> map = EconomicMap.create();
 290         // Configure with an option that enables scopes
 291         map.put(DebugOptions.DumpOnError, true);
 292         OptionValues options = new OptionValues(map);
 293         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 294         DebugContext debug = DebugContext.create(options, NO_DESCRIPTION, NO_GLOBAL_METRIC_VALUES, new PrintStream(baos), DebugHandlersFactory.LOADER);
 295         Exception e = new Exception();
 296         try {
 297             try (DebugCloseable disabled = debug.disableIntercept(); Scope s1 = debug.scope("ScopeWithDisabledIntercept")) {
 298                 try (Scope s2 = debug.scope("InnerScopeInheritsDisabledIntercept")) {
 299                     throw e;
 300                 }
 301             } catch (Throwable t) {
 302                 assert e == t;
 303                 debug.handle(t);
 304             }
 305         } catch (Throwable t) {
 306             // The exception object should propagate all the way out through
 307             // an intercept disabled scope
 308             Assert.assertEquals(e, t);
 309         }
 310         String logged = baos.toString();
 311         Assert.assertEquals("Exception should not have been intercepted", "", logged);
 312     }
 313 }