1 /*
2  * Copyright (c) 2017, 2019, 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  * @summary This test is used to check the interop compatibility on JSSE among
27  *     different JDK releases.
28  *     Note that, this is a manual test. For more details about the test and
29  *     its usages, please look through README.
30  *
31  * @library /test/lib ../TLSCommon
32  * @compile -source 1.7 -target 1.7 JdkUtils.java Server.java Client.java
33  * @run main/manual Compatibility
34  */
35 
36 import java.io.File;
37 import java.io.FileOutputStream;
38 import java.io.FileWriter;
39 import java.io.IOException;
40 import java.io.PrintStream;
41 import java.nio.file.Files;
42 import java.nio.file.Paths;
43 import java.util.ArrayList;
44 import java.util.LinkedHashMap;
45 import java.util.LinkedHashSet;
46 import java.util.List;
47 import java.util.Map;
48 import java.util.Set;
49 import java.util.concurrent.ExecutorService;
50 import java.util.concurrent.Executors;
51 import java.util.concurrent.Future;
52 import java.util.concurrent.TimeUnit;
53 import java.util.stream.Collectors;
54 import java.util.stream.Stream;
55 
56 import jdk.test.lib.process.OutputAnalyzer;
57 
58 public class Compatibility {
59 
main(String[] args)60     public static void main(String[] args) throws Throwable {
61         String javaSecurityFile
62                 = System.getProperty("test.src") + "/java.security";
63         boolean debug = Utils.getBoolProperty("debug");
64 
65         Set<JdkInfo> jdkInfos = jdkInfoList();
66 
67         System.out.println("Test start");
68 
69         List<TestCase> testCases = new ArrayList<>();
70         ExecutorService executor = Executors.newCachedThreadPool();
71         PrintStream origStdOut = System.out;
72         PrintStream origStdErr = System.err;
73 
74         try (PrintStream printStream = new PrintStream(
75                 new FileOutputStream(Utils.TEST_LOG, true))) {
76             System.setOut(printStream);
77             System.setErr(printStream);
78 
79             System.out.println(Utils.startHtml());
80             System.out.println(Utils.startPre());
81 
82             for (UseCase useCase : UseCase.getAllUseCases()) {
83                 for (JdkInfo serverJdk : jdkInfos) {
84                     Map<String, String> props = new LinkedHashMap<>();
85                     if (debug) {
86                         props.put("javax.net.debug", "all");
87                     }
88                     props.put("java.security.properties", javaSecurityFile);
89 
90                     props.put(Utils.PROP_PROTOCOL, useCase.protocol.name);
91                     props.put(Utils.PROP_CIPHER_SUITE, useCase.cipherSuite.name());
92                     props.put(Utils.PROP_CLIENT_AUTH, String.valueOf(useCase.clientAuth));
93                     if (useCase.appProtocol != UseCase.AppProtocol.NONE) {
94                         props.put(Utils.PROP_APP_PROTOCOLS,
95                                 Utils.join(Utils.VALUE_DELIMITER,
96                                         useCase.appProtocol.appProtocols));
97                         props.put(Utils.PROP_NEGO_APP_PROTOCOL,
98                                 useCase.appProtocol.negoAppProtocol);
99                     }
100                     props.put(Utils.PROP_SERVER_JDK, serverJdk.version);
101 
102                     props.put(Utils.PROP_SUPPORTS_SNI_ON_SERVER,
103                             serverJdk.supportsSNI + "");
104                     props.put(Utils.PROP_SUPPORTS_ALPN_ON_SERVER,
105                             serverJdk.supportsALPN + "");
106 
107                     for (JdkInfo clientJdk : jdkInfos) {
108                         TestCase testCase = new TestCase(serverJdk, clientJdk,
109                                 useCase);
110                         System.out.println(Utils.anchorName(testCase.toString(),
111                                 "===== Case start ====="));
112                         System.out.println(testCase.toString());
113 
114                         props.put(Utils.PROP_NEGATIVE_CASE_ON_SERVER,
115                                 testCase.negativeCaseOnServer + "");
116                         props.put(Utils.PROP_NEGATIVE_CASE_ON_CLIENT,
117                                 testCase.negativeCaseOnClient + "");
118 
119                         Future<OutputAnalyzer> serverFuture = executor.submit(() -> {
120                             return runServer(serverJdk.jdkPath, props);
121                         });
122                         int port = waitForServerStarted();
123                         System.out.println("port=" + port);
124 
125                         props.put(Utils.PROP_PORT, port + "");
126 
127                         props.put(Utils.PROP_CLIENT_JDK, clientJdk.version);
128 
129                         props.put(Utils.PROP_SUPPORTS_SNI_ON_CLIENT,
130                                 clientJdk.supportsSNI + "");
131                         props.put(Utils.PROP_SUPPORTS_ALPN_ON_CLIENT,
132                                 clientJdk.supportsALPN + "");
133                         if (useCase.serverName != UseCase.ServerName.NONE) {
134                             props.put(Utils.PROP_SERVER_NAME,
135                                     useCase.serverName.name);
136                         }
137 
138                         Status clientStatus = null;
139                         if (port != -1) {
140                             String clientOutput = runClient(clientJdk.jdkPath,
141                                     props).getOutput();
142                             clientStatus = getStatus(clientOutput);
143                         }
144 
145                         String serverOutput = serverFuture.get().getOutput();
146                         Status serverStatus = getStatus(serverOutput);
147                         testCase.setStatus(caseStatus(serverStatus, clientStatus));
148                         testCases.add(testCase);
149                         System.out.printf(
150                                 "ServerStatus=%s, ClientStatus=%s, CaseStatus=%s%n",
151                                 serverStatus, clientStatus, testCase.getStatus());
152 
153                         System.out.println("===== Case end =====");
154                     }
155                 }
156             }
157 
158             System.out.println(Utils.endPre());
159             System.out.println(Utils.endHtml());
160         }
161         System.setOut(origStdOut);
162         System.setErr(origStdErr);
163         executor.shutdown();
164 
165         System.out.println("Test end");
166         System.out.println("Report is being generated...");
167         boolean failed = generateReport(testCases);
168         System.out.println("Report is generated.");
169         if (failed) {
170             throw new RuntimeException("At least one case failed. "
171                     + "Please check logs for more details.");
172         }
173     }
174 
getStatus(String log)175     private static Status getStatus(String log) {
176         if (log.contains(Status.UNEXPECTED_SUCCESS.name())) {
177             return Status.UNEXPECTED_SUCCESS;
178         } else if (log.contains(Status.SUCCESS.name())) {
179             return Status.SUCCESS;
180         } else if (log.contains(Status.EXPECTED_FAIL.name())) {
181             return Status.EXPECTED_FAIL;
182         } else if (log.contains(Status.TIMEOUT.name())) {
183             return Status.TIMEOUT;
184         } else {
185             return Status.FAIL;
186         }
187     }
188 
caseStatus(Status serverStatus, Status clientStatus)189     private static Status caseStatus(Status serverStatus, Status clientStatus) {
190         if (clientStatus == null || clientStatus == Status.TIMEOUT) {
191             return serverStatus == Status.EXPECTED_FAIL
192                    ? Status.EXPECTED_FAIL
193                    : Status.FAIL;
194         } else if (serverStatus == Status.TIMEOUT) {
195             return clientStatus == Status.EXPECTED_FAIL
196                    ? Status.EXPECTED_FAIL
197                    : Status.FAIL;
198         } else {
199             return serverStatus == clientStatus
200                    ? serverStatus
201                    : Status.FAIL;
202         }
203     }
204 
205     // Retrieves JDK info from the file which is specified by jdkListFile.
206     // If no such file or no JDK is specified by the file, the current testing
207     // JDK will be used.
jdkInfoList()208     private static Set<JdkInfo> jdkInfoList() throws Throwable {
209         List<String> jdkList = jdkList("jdkListFile");
210         if (jdkList.size() == 0) {
211             jdkList.add(System.getProperty("test.jdk"));
212         }
213 
214         Set<JdkInfo> jdkInfoList = new LinkedHashSet<>();
215         for (String jdkPath : jdkList) {
216             JdkInfo jdkInfo = new JdkInfo(jdkPath);
217             // JDK version must be unique.
218             if (!jdkInfoList.add(jdkInfo)) {
219                 System.out.println("The JDK version is duplicate: " + jdkPath);
220             }
221         }
222         return jdkInfoList;
223     }
224 
jdkList(String listFileProp)225     private static List<String> jdkList(String listFileProp) throws IOException {
226         String listFile = System.getProperty(listFileProp);
227         System.out.println(listFileProp + "=" + listFile);
228         if (listFile != null && Files.exists(Paths.get(listFile))) {
229             try (Stream<String> lines = Files.lines(Paths.get(listFile))) {
230                 return lines.filter(line -> {
231                     return !line.trim().isEmpty();
232                 }).collect(Collectors.toList());
233             }
234         } else {
235             return new ArrayList<>();
236         }
237     }
238 
239     // Checks if server is already launched, and returns server port.
waitForServerStarted()240     private static int waitForServerStarted()
241             throws IOException, InterruptedException {
242         System.out.print("Waiting for server");
243         long deadline = System.currentTimeMillis() + Utils.TIMEOUT;
244         int port;
245         while ((port = getServerPort()) == -1
246                 && System.currentTimeMillis() < deadline) {
247             System.out.print(".");
248             TimeUnit.SECONDS.sleep(1);
249         }
250         System.out.println();
251 
252         return port;
253     }
254 
255     // Retrieves the latest server port from port.log.
getServerPort()256     private static int getServerPort() throws IOException {
257         if (!Files.exists(Paths.get(Utils.PORT_LOG))) {
258             return -1;
259         }
260 
261         try (Stream<String> lines = Files.lines(Paths.get(Utils.PORT_LOG))) {
262             return Integer.valueOf(lines.findFirst().get());
263         }
264     }
265 
runServer(String jdkPath, Map<String, String> props)266     private static OutputAnalyzer runServer(String jdkPath,
267             Map<String, String> props) {
268         return ProcessUtils.java(jdkPath, props, Server.class);
269     }
270 
runClient(String jdkPath, Map<String, String> props)271     private static OutputAnalyzer runClient(String jdkPath,
272             Map<String, String> props) {
273         return ProcessUtils.java(jdkPath, props, Client.class);
274     }
275 
276     // Generates the test result report.
generateReport(List<TestCase> testCases)277     private static boolean generateReport(List<TestCase> testCases)
278             throws IOException {
279         boolean failed = false;
280         StringBuilder report = new StringBuilder();
281         report.append(Utils.startHtml());
282         report.append(Utils.tableStyle());
283         report.append(Utils.startTable());
284         report.append(Utils.row(
285                 "No.",
286                 "ServerJDK",
287                 "ClientJDK",
288                 "Protocol",
289                 "CipherSuite",
290                 "ClientAuth",
291                 "SNI",
292                 "ALPN",
293                 "Status"));
294         for (int i = 0, size = testCases.size(); i < size; i++) {
295             TestCase testCase = testCases.get(i);
296 
297             report.append(Utils.row(
298                     Utils.anchorLink(
299                             Utils.TEST_LOG,
300                             testCase.toString(),
301                             i + ""),
302                     testCase.serverJdk.version,
303                     testCase.clientJdk.version,
304                     testCase.useCase.protocol.name,
305                     testCase.useCase.cipherSuite,
306                     Utils.boolToStr(
307                             testCase.useCase.clientAuth),
308                     Utils.boolToStr(
309                             testCase.useCase.serverName == UseCase.ServerName.EXAMPLE),
310                     Utils.boolToStr(
311                             testCase.useCase.appProtocol == UseCase.AppProtocol.EXAMPLE),
312                     testCase.getStatus()));
313             failed = failed
314                     || testCase.getStatus() == Status.FAIL
315                     || testCase.getStatus() == Status.UNEXPECTED_SUCCESS;
316         }
317         report.append(Utils.endTable());
318         report.append(Utils.endHtml());
319 
320         generateFile("report.html", report.toString());
321         return failed;
322     }
323 
generateFile(String path, String content)324     private static void generateFile(String path, String content)
325             throws IOException {
326         try(FileWriter writer = new FileWriter(new File(path))) {
327             writer.write(content);
328         }
329     }
330 }
331