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