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