1 /*
  2  * Copyright (c) 2021, 2023, 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  * @test
 26  * @bug 8284161 8287008
 27  * @summary Basic test for jcmd Thread.dump_to_file
 28  * @library /test/lib
 29  * @run junit/othervm ThreadDumpToFileTest
 30  */
 31 
 32 import java.io.IOException;
 33 import java.nio.file.Files;
 34 import java.nio.file.Path;
 35 import java.util.stream.Stream;
 36 import jdk.test.lib.dcmd.PidJcmdExecutor;
 37 import jdk.test.lib.process.OutputAnalyzer;
 38 import jdk.test.lib.threaddump.ThreadDump;
 39 
 40 import org.junit.jupiter.api.Test;
 41 import static org.junit.jupiter.api.Assertions.*;
 42 
 43 class ThreadDumpToFileTest {
 44 
 45     /**
 46      * Test thread dump, should be in plain text format.
 47      */
 48     @Test
 49     void testThreadDump() throws IOException {
 50         Path file = genThreadDumpPath(".txt");
 51         testPlainThreadDump(file);
 52     }
 53 
 54     /**
 55      * Test thread dump in plain text format.
 56      */
 57     @Test
 58     void testPlainThreadDump() throws IOException {
 59         Path file = genThreadDumpPath(".txt");
 60         testPlainThreadDump(file, "-format=plain");
 61     }
 62 
 63     /**
 64      * Test thread dump in JSON format.
 65      */
 66     @Test
 67     void testJsonThreadDump() throws IOException {
 68         Path file = genThreadDumpPath(".json");
 69         jcmdThreadDumpToFile(file, "-format=json").shouldMatch("Created");
 70 
 71         // parse the JSON text
 72         String jsonText = Files.readString(file);
 73         ThreadDump threadDump = ThreadDump.parse(jsonText);
 74 
 75         // test that the process id is this process
 76         assertTrue(threadDump.processId() == ProcessHandle.current().pid());
 77 
 78         // test that the current thread is in the root thread container
 79         var rootContainer = threadDump.rootThreadContainer();
 80         var tid = Thread.currentThread().threadId();
 81         rootContainer.findThread(tid).orElseThrow();
 82     }
 83 
 84     /**
 85      * Test that an existing file is not overwritten.
 86      */
 87     @Test
 88     void testDoNotOverwriteFile() throws IOException {
 89         Path file = genThreadDumpPath(".txt");
 90         Files.writeString(file, "xxx");
 91 
 92         jcmdThreadDumpToFile(file, "").shouldMatch("exists");
 93 
 94         // file should not be overridden
 95         assertEquals("xxx", Files.readString(file));
 96     }
 97 
 98     /**
 99      * Test overwriting an existing file.
100      */
101     @Test
102     void testOverwriteFile() throws IOException {
103         Path file = genThreadDumpPath(".txt");
104         Files.writeString(file, "xxx");
105         jcmdThreadDumpToFile(file, "-overwrite");
106     }
107 
108     /**
109      * Test thread dump in plain text format.
110      */
111     private void testPlainThreadDump(Path file, String... options) throws IOException {
112         jcmdThreadDumpToFile(file, options).shouldMatch("Created");
113 
114         // test that thread dump contains the name and id of the current thread
115         String name = Thread.currentThread().getName();
116         long tid = Thread.currentThread().threadId();
117         String expected = "#" + tid + " \"" + name + "\"";
118         assertTrue(find(file, expected), expected + " not found in " + file);
119     }
120 
121     /**
122      * Generate a file path with the given suffix to use for the thread dump.
123      */
124     private Path genThreadDumpPath(String suffix) throws IOException {
125         Path dir = Path.of(".").toAbsolutePath();
126         Path file = Files.createTempFile(dir, "threads-", suffix);
127         Files.delete(file);
128         return file;
129     }
130 
131     /**
132      * Launches jcmd Thread.dump_to_file to obtain a thread dump of this VM.
133      */
134     private OutputAnalyzer jcmdThreadDumpToFile(Path file, String... options) {
135         String cmd = "Thread.dump_to_file";
136         for (String option : options) {
137             cmd += " " + option;
138         }
139         return new PidJcmdExecutor().execute(cmd + " " + file);
140     }
141 
142     /**
143      * Returns true if the given file contains a line with the string.
144      */
145     private boolean find(Path file, String text) throws IOException {
146         try (Stream<String> stream = Files.lines(file)) {
147             return  stream.anyMatch(line -> line.indexOf(text) >= 0);
148         }
149     }
150 }