1 /* 2 * Copyright 2017 The gRPC Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package io.grpc.testing.integration; 18 19 import static junit.framework.TestCase.assertEquals; 20 import static org.junit.Assert.assertTrue; 21 22 import io.grpc.ManagedChannel; 23 import io.grpc.ManagedChannelBuilder; 24 import io.grpc.internal.MoreThrowables; 25 import io.grpc.okhttp.OkHttpChannelBuilder; 26 import java.io.IOException; 27 import java.io.PrintWriter; 28 import java.io.StringWriter; 29 import java.lang.reflect.Method; 30 import java.text.SimpleDateFormat; 31 import java.util.ArrayList; 32 import java.util.Calendar; 33 import java.util.List; 34 import java.util.Queue; 35 import java.util.concurrent.ConcurrentLinkedQueue; 36 import java.util.logging.Handler; 37 import java.util.logging.LogRecord; 38 import java.util.logging.Logger; 39 import java.util.logging.SimpleFormatter; 40 import javax.servlet.http.HttpServlet; 41 import javax.servlet.http.HttpServletRequest; 42 import javax.servlet.http.HttpServletResponse; 43 import org.junit.After; 44 import org.junit.Before; 45 import org.junit.Ignore; 46 import org.junit.Test; 47 import org.junit.internal.AssumptionViolatedException; 48 49 /** 50 * This servlet communicates with {@code grpc-test.sandbox.googleapis.com}, which is a server 51 * managed by the gRPC team. For more information, see 52 * <a href="https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md"> 53 * Interoperability Test Case Descriptions</a>. 54 */ 55 @SuppressWarnings("serial") 56 public final class OkHttpClientInteropServlet extends HttpServlet { 57 private static final String INTEROP_TEST_ADDRESS = "grpc-test.sandbox.googleapis.com:443"; 58 59 private static final class LogEntryRecorder extends Handler { 60 private Queue<LogRecord> loggedMessages = new ConcurrentLinkedQueue<>(); 61 62 @Override publish(LogRecord logRecord)63 public void publish(LogRecord logRecord) { 64 loggedMessages.add(logRecord); 65 } 66 67 @Override flush()68 public void flush() {} 69 70 @Override close()71 public void close() {} 72 getLogOutput()73 public String getLogOutput() { 74 SimpleFormatter formatter = new SimpleFormatter(); 75 StringBuilder sb = new StringBuilder(); 76 for (LogRecord loggedMessage : loggedMessages) { 77 sb.append(formatter.format(loggedMessage)); 78 } 79 return sb.toString(); 80 } 81 } 82 83 @Override doGet(HttpServletRequest req, HttpServletResponse resp)84 public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { 85 LogEntryRecorder handler = new LogEntryRecorder(); 86 Logger.getLogger("").addHandler(handler); 87 try { 88 doGetHelper(req, resp); 89 } finally { 90 Logger.getLogger("").removeHandler(handler); 91 } 92 resp.getWriter().append("=======================================\n") 93 .append("Server side java.util.logging messages:\n") 94 .append(handler.getLogOutput()); 95 } 96 doGetHelper(HttpServletRequest req, HttpServletResponse resp)97 private void doGetHelper(HttpServletRequest req, HttpServletResponse resp) throws IOException { 98 resp.setContentType("text/plain"); 99 PrintWriter writer = resp.getWriter(); 100 writer.println("Test invoked at: "); 101 writer.println(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss Z") 102 .format(Calendar.getInstance().getTime())); 103 104 // We can not use JUnit because it tries to spawn backgrounds threads. 105 // GAE+JDK7 does not allow arbitrary background threads. 106 // Let's use reflection to run test methods. 107 List<Method> befores = new ArrayList<>(); 108 List<Method> afters = new ArrayList<>(); 109 List<Method> testMethods = new ArrayList<>(); 110 int ignored = 0; 111 for (Method method : Tester.class.getMethods()) { 112 if (method.getAnnotation(Test.class) != null) { 113 if (method.getAnnotation(Ignore.class) != null) { 114 ignored++; 115 } else { 116 testMethods.add(method); 117 } 118 } else if (method.getAnnotation(Before.class) != null) { 119 befores.add(method); 120 } else if (method.getAnnotation(After.class) != null) { 121 afters.add(method); 122 } 123 } 124 125 StringBuilder sb = new StringBuilder(); 126 int failures = 0; 127 for (Method method : testMethods) { 128 // JUnit creates a new instance per test method, we will emulate that behavior. 129 Tester tester = new Tester(); 130 try { 131 for (Method before : befores) { 132 before.invoke(tester); 133 } 134 try (AutoCloseable unused = toCloseable(tester, afters)) { 135 method.invoke(tester); 136 } 137 sb.append("================\n"); 138 sb.append("PASS: Test method: ").append(method).append("\n"); 139 } catch (Throwable t) { 140 // The default JUnit4 test runner skips tests with failed assumptions. 141 // We will do the same here. 142 boolean assumptionViolated = false; 143 for (Throwable iter = t; iter != null; iter = iter.getCause()) { 144 if (iter instanceof AssumptionViolatedException) { 145 assumptionViolated = true; 146 break; 147 } 148 } 149 if (assumptionViolated) { 150 continue; 151 } 152 153 sb.append("================\n"); 154 sb.append("FAILED: Test method: ").append(method).append("\n"); 155 failures++; 156 StringWriter stringWriter = new StringWriter(); 157 PrintWriter printWriter = new PrintWriter(stringWriter); 158 t.printStackTrace(printWriter); 159 sb.append(stringWriter); 160 } 161 } 162 if (failures == 0) { 163 resp.setStatus(200); 164 writer.println( 165 String.format( 166 "PASS! Tests ran %d, tests ignored %d", 167 testMethods.size(), 168 ignored)); 169 } else { 170 resp.setStatus(500); 171 writer.println( 172 String.format( 173 "FAILED! Tests ran %d, tests failed %d, tests ignored %d", 174 testMethods.size(), 175 failures, 176 ignored)); 177 } 178 writer.println(sb); 179 } 180 toCloseable(final Object o, final List<Method> methods)181 private static AutoCloseable toCloseable(final Object o, final List<Method> methods) { 182 return new AutoCloseable() { 183 @Override 184 public void close() throws Exception { 185 Throwable failure = null; 186 for (Method method : methods) { 187 try { 188 method.invoke(o); 189 } catch (Throwable t) { 190 if (failure == null) { 191 failure = t; 192 } else { 193 failure.addSuppressed(t); 194 } 195 } 196 } 197 if (failure != null) { 198 MoreThrowables.throwIfUnchecked(failure); 199 throw new Exception(failure); 200 } 201 } 202 }; 203 } 204 205 public static final class Tester extends AbstractInteropTest { 206 @Override 207 protected ManagedChannel createChannel() { 208 assertEquals( 209 "jdk7 required", 210 "1.7", 211 System.getProperty("java.specification.version")); 212 assertEquals( 213 "Can not run in dev servers because they lack org.conscrypt.OpenSSLProvider support", 214 "Production", 215 System.getProperty("com.google.appengine.runtime.environment")); 216 ManagedChannelBuilder<?> builder = 217 ManagedChannelBuilder.forTarget(INTEROP_TEST_ADDRESS) 218 .maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE); 219 assertTrue(builder instanceof OkHttpChannelBuilder); 220 return builder.build(); 221 } 222 223 @Override 224 protected boolean metricsExpected() { 225 // Server-side metrics won't be found because the server is running remotely. 226 return false; 227 } 228 229 // grpc-test.sandbox.googleapis.com does not support these tests 230 @Ignore 231 @Override 232 public void customMetadata() { } 233 234 @Ignore 235 @Override 236 public void statusCodeAndMessage() { } 237 238 @Ignore 239 @Override 240 public void exchangeMetadataUnaryCall() { } 241 242 @Ignore 243 @Override 244 public void exchangeMetadataStreamingCall() { } 245 246 @Ignore 247 @Override 248 public void specialStatusMessage() {} 249 250 // grpc-java/issues/4626: this test has become flakey on GAE JDK7 251 @Ignore 252 @Override 253 public void timeoutOnSleepingServer() {} 254 } 255 } 256