22
33import static com .google .common .truth .Truth .assertThat ;
44import static com .google .common .truth .Truth .assertWithMessage ;
5+ import static java .util .stream .Collectors .toList ;
56
67import com .google .auto .value .AutoValue ;
78import com .google .cloud .functions .invoker .runner .Invoker ;
9+ import com .google .common .collect .ImmutableList ;
810import com .google .common .collect .ImmutableMap ;
9- import com .google .common .io . Files ;
11+ import com .google .common .collect . Iterables ;
1012import com .google .common .io .Resources ;
1113import com .google .gson .Gson ;
1214import com .google .gson .JsonObject ;
1921import java .net .ServerSocket ;
2022import java .net .URL ;
2123import java .nio .charset .StandardCharsets ;
24+ import java .nio .file .Files ;
25+ import java .nio .file .Path ;
26+ import java .nio .file .Paths ;
2227import java .util .Arrays ;
28+ import java .util .List ;
2329import java .util .Map ;
2430import java .util .concurrent .CountDownLatch ;
2531import java .util .concurrent .TimeUnit ;
32+ import java .util .regex .Pattern ;
2633import org .eclipse .jetty .client .HttpClient ;
2734import org .eclipse .jetty .client .api .ContentResponse ;
2835import org .eclipse .jetty .client .api .Request ;
@@ -163,6 +170,53 @@ public void packageless() throws Exception {
163170 TestCase .builder ().setExpectedResponseText ("hello, world\n " ).build ());
164171 }
165172
173+ /** Any runtime class that user code shouldn't be able to see. */
174+ private static final Class <?> INTERNAL_CLASS = CloudFunction .class ;
175+
176+ private String functionJarString () throws IOException {
177+ Path functionJarTargetDir = Paths .get ("../functionjar/target" );
178+ Pattern functionJarPattern = Pattern .compile ("java-function-invoker-core-functionjar-.*\\ .jar" );
179+ List <Path > functionJars = Files .list (functionJarTargetDir )
180+ .map (path -> path .getFileName ().toString ())
181+ .filter (s -> functionJarPattern .matcher (s ).matches ())
182+ .map (s -> functionJarTargetDir .resolve (s ))
183+ .collect (toList ());
184+ assertWithMessage ("Number of jars in %s matching %s" , functionJarTargetDir , functionJarPattern )
185+ .that (functionJars ).hasSize (1 );
186+ return Iterables .getOnlyElement (functionJars ).toString ();
187+ }
188+
189+ /**
190+ * Tests that if we launch an HTTP function with {@code -jar}, then the function code cannot
191+ * see the classes from the runtime. This is allows us to avoid conflicts between versions of
192+ * libraries that we use in the runtime and different versions of the same libraries that the
193+ * function might use.
194+ */
195+ @ Test
196+ public void jarOptionHttp () throws Exception {
197+ testHttpFunction ("com.example.functionjar.Foreground" ,
198+ ImmutableList .of ("-jar" , functionJarString ()),
199+ TestCase .builder ()
200+ .setUrl ("/?class=" + INTERNAL_CLASS .getName ())
201+ .setExpectedResponseText ("OK" )
202+ .build ());
203+ }
204+
205+ /** Like {@link #jarOptionHttp} but for background functions. */
206+ @ Test
207+ public void jarOptionBackground () throws Exception {
208+ Gson gson = new Gson ();
209+ URL resourceUrl = getClass ().getResource ("/adder_gcf_ga_event.json" );
210+ assertThat (resourceUrl ).isNotNull ();
211+ String originalJson = Resources .toString (resourceUrl , StandardCharsets .UTF_8 );
212+ JsonObject json = gson .fromJson (originalJson , JsonObject .class );
213+ JsonObject jsonData = json .getAsJsonObject ("data" );
214+ jsonData .addProperty ("class" , INTERNAL_CLASS .getName ());
215+ testBackgroundFunction ("com.example.functionjar.Background" ,
216+ ImmutableList .of ("-jar" , functionJarString ()),
217+ TestCase .builder ().setRequestText (json .toString ()).build ());
218+ }
219+
166220 // In these tests, we test a number of different functions that express the same functionality
167221 // in different ways. Each function is invoked with a complete HTTP body that looks like a real
168222 // event. We start with a fixed body and insert into its JSON an extra property that tells the
@@ -180,23 +234,36 @@ private void backgroundTest(String functionTarget) throws Exception {
180234 jsonData .addProperty ("targetFile" , snoopFile .toString ());
181235 testBackgroundFunction (functionTarget ,
182236 TestCase .builder ().setRequestText (json .toString ()).build ());
183- String snooped = Files .asCharSource (snoopFile , StandardCharsets .UTF_8 ). read ( );
237+ String snooped = new String ( Files .readAllBytes (snoopFile . toPath ()) , StandardCharsets .UTF_8 );
184238 JsonObject snoopedJson = gson .fromJson (snooped , JsonObject .class );
185239 assertThat (snoopedJson ).isEqualTo (json );
186240 }
187241
188242 private void testHttpFunction (String target , TestCase ... testCases ) throws Exception {
189- testFunction (SignatureType .HTTP , target , testCases );
243+ testHttpFunction (target , ImmutableList .of (), testCases );
244+ }
245+
246+ private void testHttpFunction (
247+ String target , ImmutableList <String > extraArgs , TestCase ... testCases ) throws Exception {
248+ testFunction (SignatureType .HTTP , target , extraArgs , testCases );
190249 }
191250
192251 private void testBackgroundFunction (String classAndMethod , TestCase ... testCases )
193252 throws Exception {
194- testFunction (SignatureType .BACKGROUND , classAndMethod , testCases );
253+ testBackgroundFunction (classAndMethod , ImmutableList .of (), testCases );
254+ }
255+ private void testBackgroundFunction (
256+ String classAndMethod , ImmutableList <String > extraArgs , TestCase ... testCases )
257+ throws Exception {
258+ testFunction (SignatureType .BACKGROUND , classAndMethod , extraArgs , testCases );
195259 }
196260
197261 private void testFunction (
198- SignatureType signatureType , String target , TestCase ... testCases ) throws Exception {
199- Process server = startServer (signatureType , target );
262+ SignatureType signatureType ,
263+ String target ,
264+ ImmutableList <String > extraArgs ,
265+ TestCase ... testCases ) throws Exception {
266+ Process server = startServer (signatureType , target , extraArgs );
200267 try {
201268 HttpClient httpClient = new HttpClient ();
202269 httpClient .start ();
@@ -233,7 +300,8 @@ public String toString() {
233300 }
234301 }
235302
236- private Process startServer (SignatureType signatureType , String target )
303+ private Process startServer (
304+ SignatureType signatureType , String target , ImmutableList <String > extraArgs )
237305 throws IOException , InterruptedException {
238306 File javaHome = new File (System .getProperty ("java.home" ));
239307 assertThat (javaHome .exists ()).isTrue ();
@@ -242,9 +310,10 @@ private Process startServer(SignatureType signatureType, String target)
242310 assertThat (javaCommand .exists ()).isTrue ();
243311 String myClassPath = System .getProperty ("java.class.path" );
244312 assertThat (myClassPath ).isNotNull ();
245- String [] command = {
246- javaCommand .toString (), "-classpath" , myClassPath , Invoker .class .getName (),
247- };
313+ ImmutableList <String > command = ImmutableList .<String >builder ()
314+ .add (javaCommand .toString (), "-classpath" , myClassPath , Invoker .class .getName ())
315+ .addAll (extraArgs )
316+ .build ();
248317 ProcessBuilder processBuilder = new ProcessBuilder ()
249318 .command (command )
250319 .redirectErrorStream (true );
@@ -256,7 +325,8 @@ private Process startServer(SignatureType signatureType, String target)
256325 Process serverProcess = processBuilder .start ();
257326 CountDownLatch ready = new CountDownLatch (1 );
258327 new Thread (() -> monitorOutput (serverProcess .getInputStream (), ready )).start ();
259- ready .await (5 , TimeUnit .SECONDS );
328+ boolean serverReady = ready .await (5 , TimeUnit .SECONDS );
329+ assertWithMessage ("Waiting for server to be ready" ).that (serverReady ).isTrue ();
260330 return serverProcess ;
261331 }
262332
0 commit comments