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         EconomicMap<OptionKey<?>, Object> map = EconomicMap.create();
 201         // Configure with an option that enables scopes
 202         map.put(DebugOptions.DumpOnError, true);
 203         OptionValues options = new OptionValues(map);
 204         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 205         DebugContext debug = DebugContext.create(options, NO_DESCRIPTION, NO_GLOBAL_METRIC_VALUES, new PrintStream(baos), DebugHandlersFactory.LOADER);
 206         Exception e = new Exception("testEnabledSandbox");
 207         String scopeName = "";
 208         try {
 209             try (DebugContext.Scope d = debug.sandbox("TestExceptionHandling", debug.getConfig())) {
 210                 scopeName = d.getQualifiedName();
 211                 throw e;
 212             } catch (Throwable t) {
 213                 assert e == t;
 214                 debug.handle(t);
 215             }
 216         } catch (Throwable t) {
 217             // The exception object should propagate all the way out through
 218             // a enabled sandbox scope
 219             Assert.assertEquals(e, t);
 220         }
 221         String logged = baos.toString();
 222         String expected = String.format("Exception raised in scope %s: %s", scopeName, e);
 223         String line = "-------------------------------------------------------";
 224         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));
 225     }
 226 
 227     @Test
 228     public void testDisabledSandbox() {
 229         EconomicMap<OptionKey<?>, Object> map = EconomicMap.create();
 230         // Configure with an option that enables scopes
 231         map.put(DebugOptions.DumpOnError, true);
 232         OptionValues options = new OptionValues(map);
 233         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 234         DebugContext debug = DebugContext.create(options, NO_DESCRIPTION, NO_GLOBAL_METRIC_VALUES, new PrintStream(baos), DebugHandlersFactory.LOADER);
 235         Exception e = new Exception("testDisabledSandbox");
 236         try {
 237             // Test a disabled sandbox scope
 238             try (DebugContext.Scope d = debug.sandbox("TestExceptionHandling", null)) {
 239                 throw e;
 240             } catch (Throwable t) {
 241                 assert e == t;
 242                 debug.handle(t);
 243             }
 244         } catch (Throwable t) {
 245             // The exception object should propagate all the way out through
 246             // a disabled sandbox scope
 247             Assert.assertEquals(e, t);
 248         }
 249         String logged = baos.toString();
 250         Assert.assertTrue(logged, logged.isEmpty());
 251     }
 252 
 253     /**
 254      * Tests that using a {@link DebugContext} on a thread other than the one on which it was
 255      * created causes an assertion failure.
 256      */
 257     @Test
 258     public void testInvariantChecking() throws InterruptedException {
 259         Assume.assumeTrue(Assertions.assertionsEnabled());
 260         EconomicMap<OptionKey<?>, Object> map = EconomicMap.create();
 261         // Configure with an option that enables counters
 262         map.put(DebugOptions.Counters, "");
 263         OptionValues options = new OptionValues(map);
 264         DebugContext debug = DebugContext.create(options, DebugHandlersFactory.LOADER);
 265         CounterKey counter = DebugContext.counter("DebugContextTestCounter");
 266         AssertionError[] result = {null};
 267         Thread thread = new Thread() {
 268 
 269             @Override
 270             public void run() {
 271                 try {
 272                     counter.add(debug, 1);
 273                 } catch (AssertionError e) {
 274                     result[0] = e;
 275                 }
 276             }
 277         };
 278         thread.start();
 279         thread.join();
 280 
 281         Assert.assertNotNull("Expected thread to throw AssertionError", result[0]);
 282     }
 283 
 284     @Test
 285     public void testDisableIntercept() {
 286         EconomicMap<OptionKey<?>, Object> map = EconomicMap.create();
 287         // Configure with an option that enables scopes
 288         map.put(DebugOptions.DumpOnError, true);
 289         OptionValues options = new OptionValues(map);
 290         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 291         DebugContext debug = DebugContext.create(options, NO_DESCRIPTION, NO_GLOBAL_METRIC_VALUES, new PrintStream(baos), DebugHandlersFactory.LOADER);
 292         Exception e = new Exception();
 293         try {
 294             try (DebugCloseable disabled = debug.disableIntercept(); Scope s1 = debug.scope("ScopeWithDisabledIntercept")) {
 295                 try (Scope s2 = debug.scope("InnerScopeInheritsDisabledIntercept")) {
 296                     throw e;
 297                 }
 298             } catch (Throwable t) {
 299                 assert e == t;
 300                 debug.handle(t);
 301             }
 302         } catch (Throwable t) {
 303             // The exception object should propagate all the way out through
 304             // an intercept disabled scope
 305             Assert.assertEquals(e, t);
 306         }
 307         String logged = baos.toString();
 308         Assert.assertEquals("Exception should not have been intercepted", "", logged);
 309     }
 310 }