Add log4j2 agent tests
[lttng-ust-java-tests.git] / lttng-ust-java-tests-common / src / main / java / org / lttng / ust / agent / integration / context / AppContextITBase.java
CommitLineData
73fb6785
AM
1/*
2 * Copyright (C) 2016, EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19package org.lttng.ust.agent.integration.context;
20
21import static org.junit.Assert.assertFalse;
22import static org.junit.Assert.assertNotNull;
23import static org.junit.Assert.assertTrue;
24import static org.junit.Assert.fail;
25
26import java.io.IOException;
27import java.util.List;
28
29import org.junit.After;
30import org.junit.Before;
31import org.junit.Test;
32import org.junit.runner.RunWith;
33import org.lttng.tools.ILttngSession;
34import org.lttng.tools.ILttngSession.Domain;
35import org.lttng.ust.agent.ILttngHandler;
36import org.lttng.ust.agent.context.ContextInfoManager;
37import org.lttng.ust.agent.utils.TestPrintRunner;
38
39/**
40 * Base abstract class to implement all sorts of integration tests verifying the
41 * presence of enabled application contexts in resulting traces.
42 */
43@RunWith(TestPrintRunner.class)
44public abstract class AppContextITBase {
45
46 protected static final String EVENT_NAME = "EventName";
47
48 protected static final String RETRIEVER_NAME_1 = "Retriever1";
5ab95f21 49 protected static final String RETRIEVER_NAME_2 = "some.retriever_2";
73fb6785
AM
50
51 private static final String CONTEXT_NAME = ContextInfoRetrieverStubs.CONTEXT_NAME;
52
53 private ContextInfoManager cim;
54 private ILttngSession session;
55
56 /* Field defined by the sub-class */
57 protected ILttngHandler logHandler;
58
59 protected abstract Domain getDomain();
60
eca1a136
MJ
61 protected abstract boolean closeHandlers();
62
73fb6785
AM
63 protected abstract void sendEventsToLoggers();
64
65 /**
66 * Base test setup
67 */
68 @Before
69 public void testSetup() {
70 try {
71 cim = ContextInfoManager.getInstance();
72 } catch (SecurityException | IOException e) {
73 /* The native library is not available! */
1f3dd43c 74 fail(e.getMessage());
73fb6785
AM
75 }
76 session = ILttngSession.createSession(null, getDomain());
77 }
78
79 /**
80 * Base test teardown
81 */
82 @After
83 public void testTeardown() {
84 session.close();
85
eca1a136
MJ
86 if (closeHandlers()) {
87 logHandler.close();
88 }
73fb6785
AM
89 logHandler = null;
90
91 /* In case some tests fail or forget to unregister their retrievers */
92 cim.unregisterContextInfoRetriever(RETRIEVER_NAME_1);
93 cim.unregisterContextInfoRetriever(RETRIEVER_NAME_2);
94 }
95
96 // ------------------------------------------------------------------------
97 // Context enabled/disabled tests
98 // ------------------------------------------------------------------------
99
100 /**
101 * Utility method to check that a context is present in all events of a
102 * trace output.
103 */
104 private static void testContextPresentInTrace(List<String> traceOutput, String retrieverName, String contextName, String contextValue) {
5ab95f21
AM
105 String traceRetrieverName = convertToNameInTrace(retrieverName);
106 String traceContextName = convertToNameInTrace(contextName);
107
108 String fullString = "_app_" + traceRetrieverName + "_" + traceContextName + " = " + contextValue;
73fb6785
AM
109 traceOutput.forEach(line -> assertTrue(line.contains(fullString)));
110 }
111
112 /**
113 * Utility method to check that a context is *absent* from all events of a
114 * trace output
115 */
116 private static void testContextNotPresentInTrace(List<String> traceOutput, String retrieverName, String contextName) {
5ab95f21
AM
117 String traceRetrieverName = convertToNameInTrace(retrieverName);
118 String traceContextName = convertToNameInTrace(contextName);
119
120 String fullString = "_app_" + traceRetrieverName + "_" + traceContextName;
73fb6785
AM
121 traceOutput.forEach(line -> assertFalse(line.contains(fullString)));
122 }
123
5ab95f21
AM
124 /**
125 * LTTng accepts periods in context names, but ends up printing them as
126 * underscores in the trace, so the metadata grammar remains valid.
127 */
128 private static String convertToNameInTrace(String name) {
129 return name.replace('.', '_');
130 }
131
73fb6785
AM
132 /**
133 * Test that if no retrievers are declared, no context info is passed at
134 * all.
135 */
136 @Test
137 public void testNoContexts() {
138 assertTrue(session.enableAllEvents());
139 assertTrue(session.start());
140 sendEventsToLoggers();
141 assertTrue(session.stop());
142
143 List<String> output = session.view();
144 assertNotNull(output);
145 assertFalse(output.isEmpty());
146
147 /* Test that there is no "_app" contexts in the output */
148 output.forEach(line -> assertFalse(line.contains("_app")));
149 }
150
151 /**
152 * Test that if a retriever is registered and provides a context, but this
153 * context is not enabled in the tracing session, that it is not present in
154 * the trace.
155 */
156 @Test
157 public void testContextAvailableButNotEnabled() {
158 assertTrue(cim.registerContextInfoRetriever(RETRIEVER_NAME_1, ContextInfoRetrieverStubs.STRING_RETRIEVER));
159
160 assertTrue(session.enableAllEvents());
161 assertTrue(session.start());
162 sendEventsToLoggers();
163 assertTrue(session.stop());
164
165 List<String> output = session.view();
166 assertNotNull(output);
167 assertFalse(output.isEmpty());
168
169 /* Test that there is no "_app" contexts in the output */
170 output.forEach(line -> assertFalse(line.contains("_app")));
171
172 assertTrue(cim.unregisterContextInfoRetriever(RETRIEVER_NAME_1));
173 }
174
175 /**
176 * Test that if a context is enabled, but no retriever provides it, that the
177 * retriever/context names are still mentioned in the event but no value is
178 * provided.
179 */
180 @Test
181 public void testContextNotAvailableButEnabled() {
182 assertTrue(session.enableAllEvents());
183 assertTrue(session.enableAppContext(RETRIEVER_NAME_1, CONTEXT_NAME));
184
185 assertTrue(session.start());
186 sendEventsToLoggers();
187 assertTrue(session.stop());
188
189 List<String> output = session.view();
190 assertNotNull(output);
191 assertFalse(output.isEmpty());
192
193 /* Test that context name is there but value is not */
fd2b8059 194 testContextPresentInTrace(output, RETRIEVER_NAME_1, CONTEXT_NAME, "{ { } } }");
73fb6785
AM
195 }
196
197 /**
198 * Test that if a context is enabled and provided by a retriever that it is
199 * correctly present in the tracing session.
200 */
201 @Test
202 public void testContextAvailableAndEnabled() {
203 assertTrue(cim.registerContextInfoRetriever(RETRIEVER_NAME_1, ContextInfoRetrieverStubs.STRING_RETRIEVER));
204
205 assertTrue(session.enableAllEvents());
206 assertTrue(session.enableAppContext(RETRIEVER_NAME_1, CONTEXT_NAME));
207
208 assertTrue(session.start());
209 sendEventsToLoggers();
210 assertTrue(session.stop());
211
212 List<String> output = session.view();
213 assertNotNull(output);
214 assertFalse(output.isEmpty());
215
216 /* Test that context name + value are present */
217 testContextPresentInTrace(output, RETRIEVER_NAME_1, CONTEXT_NAME,
fd2b8059 218 "{ \"" + ContextInfoRetrieverStubs.STRING_VALUE + "\" }");
73fb6785
AM
219
220 assertTrue(cim.unregisterContextInfoRetriever(RETRIEVER_NAME_1));
221 }
222
223 /**
224 * Test that one context is available by a retriever, but another is is
225 * enabled in the session. Only the latter should be mentioned in events,
226 * with no value.
227 */
228 @Test
229 public void testContextsOneAvailableOtherEnabled() {
230 assertTrue(cim.registerContextInfoRetriever(RETRIEVER_NAME_1, ContextInfoRetrieverStubs.STRING_RETRIEVER));
231
232 assertTrue(session.enableAllEvents());
233 assertTrue(session.enableAppContext(RETRIEVER_NAME_2, CONTEXT_NAME));
234
235 assertTrue(session.start());
236 sendEventsToLoggers();
237 assertTrue(session.stop());
238
239 List<String> output = session.view();
240 assertNotNull(output);
241 assertFalse(output.isEmpty());
242
243 /* Test that only retriever-name-2 is present, with no value */
244 testContextNotPresentInTrace(output, RETRIEVER_NAME_1, CONTEXT_NAME);
fd2b8059 245 testContextPresentInTrace(output, RETRIEVER_NAME_2, CONTEXT_NAME, "{ { } } }");
73fb6785
AM
246
247 assertTrue(cim.unregisterContextInfoRetriever(RETRIEVER_NAME_1));
248 }
249
250 /**
251 * Test with two contexts provided in the application, but only one of them
252 * is enabled in the session. Only that one should be present in the trace,
253 * name and value.
254 */
255 @Test
256 public void testContextsTwoAvailableOneEnabled() {
257 assertTrue(cim.registerContextInfoRetriever(RETRIEVER_NAME_1, ContextInfoRetrieverStubs.STRING_RETRIEVER));
258 assertTrue(cim.registerContextInfoRetriever(RETRIEVER_NAME_2, ContextInfoRetrieverStubs.INTEGER_RETRIEVER));
259
260 assertTrue(session.enableAllEvents());
261 assertTrue(session.enableAppContext(RETRIEVER_NAME_1, CONTEXT_NAME));
262
263 assertTrue(session.start());
264 sendEventsToLoggers();
265 assertTrue(session.stop());
266
267 List<String> output = session.view();
268 assertNotNull(output);
269 assertFalse(output.isEmpty());
270
271 /* Test that only retriever-name-1 is present, name + value */
272 testContextPresentInTrace(output, RETRIEVER_NAME_1, CONTEXT_NAME,
fd2b8059 273 "{ \"" + ContextInfoRetrieverStubs.STRING_VALUE + "\" }");
73fb6785
AM
274 testContextNotPresentInTrace(output, RETRIEVER_NAME_2, CONTEXT_NAME);
275
276 assertTrue(cim.unregisterContextInfoRetriever(RETRIEVER_NAME_1));
277 assertTrue(cim.unregisterContextInfoRetriever(RETRIEVER_NAME_2));
278 }
279
280 /**
281 * Test with two contexts enabled in the session but only one of them is
282 * provided by the application. Both should be mentioned in the trace, but
283 * only the provided one will have a value.
284 */
285 @Test
286 public void testContextsOneAvailableTwoEnabled() {
287 assertTrue(cim.registerContextInfoRetriever(RETRIEVER_NAME_1, ContextInfoRetrieverStubs.STRING_RETRIEVER));
288
289 assertTrue(session.enableAllEvents());
290 assertTrue(session.enableAppContext(RETRIEVER_NAME_1, CONTEXT_NAME));
291 assertTrue(session.enableAppContext(RETRIEVER_NAME_2, CONTEXT_NAME));
292
293 assertTrue(session.start());
294 sendEventsToLoggers();
295 assertTrue(session.stop());
296
297 List<String> output = session.view();
298 assertNotNull(output);
299 assertFalse(output.isEmpty());
300
301 /* Test that both contexts are present, but only retriever-1's has a value */
302 testContextPresentInTrace(output, RETRIEVER_NAME_1, CONTEXT_NAME,
fd2b8059
JR
303 "{ \"" + ContextInfoRetrieverStubs.STRING_VALUE + "\" }");
304 testContextPresentInTrace(output, RETRIEVER_NAME_2, CONTEXT_NAME, "{ { } } }");
73fb6785
AM
305
306 assertTrue(cim.unregisterContextInfoRetriever(RETRIEVER_NAME_1));
307 }
308
309 // ------------------------------------------------------------------------
310 // Context types tests
311 // ------------------------------------------------------------------------
312
313 /**
314 * Utility method to enable all events, add the one context we are looking
315 * for, take a trace, and return the trace output.
316 */
317 private List<String> enableContextAndTrace() {
318 assertTrue(session.enableAllEvents());
319 assertTrue(session.enableAppContext(RETRIEVER_NAME_1, CONTEXT_NAME));
320 assertTrue(session.start());
321 sendEventsToLoggers();
322 assertTrue(session.stop());
323
324 List<String> output = session.view();
325 assertNotNull(output);
326 assertFalse(output.isEmpty());
327
328 return output;
329 }
330
331 /**
332 * Test a "null" context value.
333 */
334 @Test
335 public void testContextValueNull() {
336 assertTrue(cim.registerContextInfoRetriever(RETRIEVER_NAME_1, ContextInfoRetrieverStubs.NULL_RETRIEVER));
337
338 List<String> output = enableContextAndTrace();
fd2b8059 339 testContextPresentInTrace(output, RETRIEVER_NAME_1, CONTEXT_NAME, "{ { } } }");
73fb6785
AM
340
341 assertTrue(cim.unregisterContextInfoRetriever(RETRIEVER_NAME_1));
342 }
343
344 /**
345 * Test an integer (int32) context value.
346 */
347 @Test
348 public void testContextValueInteger() {
349 assertTrue(cim.registerContextInfoRetriever(RETRIEVER_NAME_1, ContextInfoRetrieverStubs.INTEGER_RETRIEVER));
350
351 List<String> output = enableContextAndTrace();
352 testContextPresentInTrace(output, RETRIEVER_NAME_1, CONTEXT_NAME,
fd2b8059 353 "{ " + ContextInfoRetrieverStubs.INTEGER_VALUE + " } }");
73fb6785
AM
354
355 assertTrue(cim.unregisterContextInfoRetriever(RETRIEVER_NAME_1));
356 }
357
358 /**
359 * Test a long (int64) context value.
360 */
361 @Test
362 public void testContextValueLong() {
363 assertTrue(cim.registerContextInfoRetriever(RETRIEVER_NAME_1, ContextInfoRetrieverStubs.LONG_RETRIEVER));
364
365 List<String> output = enableContextAndTrace();
366 testContextPresentInTrace(output, RETRIEVER_NAME_1, CONTEXT_NAME,
fd2b8059 367 "{ " + ContextInfoRetrieverStubs.LONG_VALUE + " } }");
73fb6785
AM
368
369 assertTrue(cim.unregisterContextInfoRetriever(RETRIEVER_NAME_1));
370 }
371
372 /**
373 * Test a double context value.
374 */
375 @Test
376 public void testContextValueDouble() {
377 assertTrue(cim.registerContextInfoRetriever(RETRIEVER_NAME_1, ContextInfoRetrieverStubs.DOUBLE_RETRIEVER));
378
379 List<String> output = enableContextAndTrace();
380 testContextPresentInTrace(output, RETRIEVER_NAME_1, CONTEXT_NAME,
fd2b8059 381 "{ " + ContextInfoRetrieverStubs.DOUBLE_VALUE + " } }");
73fb6785
AM
382
383 assertTrue(cim.unregisterContextInfoRetriever(RETRIEVER_NAME_1));
384 }
385
386 /**
387 * Test a character context value (should get converted to a string).
388 */
389 @Test
390 public void testContextValueCharacter() {
391 assertTrue(cim.registerContextInfoRetriever(RETRIEVER_NAME_1, ContextInfoRetrieverStubs.CHARACTER_RETRIEVER));
392
393 List<String> output = enableContextAndTrace();
394 testContextPresentInTrace(output, RETRIEVER_NAME_1, CONTEXT_NAME,
fd2b8059 395 "{ \"" + ContextInfoRetrieverStubs.CHARACTER_VALUE + "\" } }");
73fb6785
AM
396
397 assertTrue(cim.unregisterContextInfoRetriever(RETRIEVER_NAME_1));
398 }
399
400 /**
401 * Test a float context value.
402 */
403 @Test
404 public void testContextValueFloat() {
405 assertTrue(cim.registerContextInfoRetriever(RETRIEVER_NAME_1, ContextInfoRetrieverStubs.FLOAT_RETRIEVER));
406
407 List<String> output = enableContextAndTrace();
408 testContextPresentInTrace(output, RETRIEVER_NAME_1, CONTEXT_NAME,
fd2b8059 409 "{ " + ContextInfoRetrieverStubs.FLOAT_VALUE + " } }");
73fb6785
AM
410
411 assertTrue(cim.unregisterContextInfoRetriever(RETRIEVER_NAME_1));
412 }
413
414 /**
415 * Test a byte (int8) context value.
416 */
417 @Test
418 public void testContextValueByte() {
419 assertTrue(cim.registerContextInfoRetriever(RETRIEVER_NAME_1, ContextInfoRetrieverStubs.BYTE_RETRIEVER));
420
421 List<String> output = enableContextAndTrace();
422 testContextPresentInTrace(output, RETRIEVER_NAME_1, CONTEXT_NAME,
fd2b8059 423 "{ " + ContextInfoRetrieverStubs.BYTE_VALUE + " } }");
73fb6785
AM
424
425 assertTrue(cim.unregisterContextInfoRetriever(RETRIEVER_NAME_1));
426 }
427
428 /**
429 * Test a short (int16) context value.
430 */
431 @Test
432 public void testContextValueShort() {
433 assertTrue(cim.registerContextInfoRetriever(RETRIEVER_NAME_1, ContextInfoRetrieverStubs.SHORT_RETRIEVER));
434
435 List<String> output = enableContextAndTrace();
436 testContextPresentInTrace(output, RETRIEVER_NAME_1, CONTEXT_NAME,
fd2b8059 437 "{ " + ContextInfoRetrieverStubs.SHORT_VALUE + " } }");
73fb6785
AM
438
439 assertTrue(cim.unregisterContextInfoRetriever(RETRIEVER_NAME_1));
440 }
441
442 /**
443 * Test a "true" boolean context value (gets converted to a int8 of value 1).
444 */
445 @Test
446 public void testContextValueBooleanTrue() {
447 assertTrue(cim.registerContextInfoRetriever(RETRIEVER_NAME_1, ContextInfoRetrieverStubs.BOOLEAN_TRUE_RETRIEVER));
448
449 List<String> output = enableContextAndTrace();
fd2b8059 450 testContextPresentInTrace(output, RETRIEVER_NAME_1, CONTEXT_NAME, "{ 1 } }");
73fb6785
AM
451
452 assertTrue(cim.unregisterContextInfoRetriever(RETRIEVER_NAME_1));
453 }
454
455 /**
456 * Test a "false" boolean context value (gets converted to a int8 of value 0).
457 */
458 @Test
459 public void testContextValueBooleanFalse() {
460 assertTrue(cim.registerContextInfoRetriever(RETRIEVER_NAME_1, ContextInfoRetrieverStubs.BOOLEAN_FALSE_RETRIEVER));
461
462 List<String> output = enableContextAndTrace();
fd2b8059 463 testContextPresentInTrace(output, RETRIEVER_NAME_1, CONTEXT_NAME, "{ 0 } }");
73fb6785
AM
464
465 assertTrue(cim.unregisterContextInfoRetriever(RETRIEVER_NAME_1));
466 }
467
468 /**
469 * Test a string context value.
470 */
471 @Test
472 public void testContextValueString() {
473 assertTrue(cim.registerContextInfoRetriever(RETRIEVER_NAME_1, ContextInfoRetrieverStubs.STRING_RETRIEVER));
474
475 List<String> output = enableContextAndTrace();
476 testContextPresentInTrace(output, RETRIEVER_NAME_1, CONTEXT_NAME,
fd2b8059 477 "{ \"" + ContextInfoRetrieverStubs.STRING_VALUE + "\" } }");
73fb6785
AM
478
479 assertTrue(cim.unregisterContextInfoRetriever(RETRIEVER_NAME_1));
480 }
481
482 /**
483 * Test a Object context value (should be converted to a String via .toString()).
484 */
485 @Test
486 public void testContextValueObject() {
487 assertTrue(cim.registerContextInfoRetriever(RETRIEVER_NAME_1, ContextInfoRetrieverStubs.OBJECT_RETRIEVER));
488
489 List<String> output = enableContextAndTrace();
490 testContextPresentInTrace(output, RETRIEVER_NAME_1, CONTEXT_NAME,
fd2b8059 491 "{ \"" + ContextInfoRetrieverStubs.OBJECT_VALUE.toString() + "\" } }");
73fb6785
AM
492
493 assertTrue(cim.unregisterContextInfoRetriever(RETRIEVER_NAME_1));
494 }
8c202b54
AM
495
496 // ------------------------------------------------------------------------
497 // Tests related to filtering
498 // ------------------------------------------------------------------------
499
500 /**
501 * Test with a filter expression using a context, but not having the actual
502 * context enabled.
503 *
504 * The JNI should still send the context so UST can use it for filtering,
505 * but it should not be present in the resulting trace.
506 */
507 @Test
508 public void testContextFilterExpressionNotEnabled() {
509 assertTrue(cim.registerContextInfoRetriever(RETRIEVER_NAME_1, ContextInfoRetrieverStubs.STRING_RETRIEVER));
510
511 assertTrue(session.enableEvent(EVENT_NAME, null, false,
d838d835 512 "$app." + RETRIEVER_NAME_1 + ':' + CONTEXT_NAME + "==\"" + ContextInfoRetrieverStubs.STRING_VALUE + '\"'));
8c202b54
AM
513
514 assertTrue(session.start());
515 sendEventsToLoggers();
516 assertTrue(session.stop());
517
518 List<String> output = session.view();
519 assertNotNull(output);
520 assertFalse(output.isEmpty());
521
522 testContextNotPresentInTrace(output, RETRIEVER_NAME_1, CONTEXT_NAME);
523
524 assertTrue(cim.unregisterContextInfoRetriever(RETRIEVER_NAME_1));
525 }
526
527 /**
528 * Test with a filter expression and an enabled context. The filter however
529 * should *exclude* the events, so no events should be present in the
530 * resulting trace.
531 */
532 @Test
533 public void testContextFilterExpressionEnabledNotMatching() {
534 assertTrue(cim.registerContextInfoRetriever(RETRIEVER_NAME_1, ContextInfoRetrieverStubs.STRING_RETRIEVER));
535
536 assertTrue(session.enableEvent(EVENT_NAME, null, false,
d838d835 537 "$app." + RETRIEVER_NAME_1 + ':' + CONTEXT_NAME + "!=\"" + ContextInfoRetrieverStubs.STRING_VALUE + '\"'));
8c202b54
AM
538
539 assertTrue(session.enableAppContext(RETRIEVER_NAME_1, CONTEXT_NAME));
540 assertTrue(session.start());
541 sendEventsToLoggers();
542 assertTrue(session.stop());
543
544 List<String> output = session.view();
545 assertNotNull(output);
546 assertTrue(output.isEmpty());
547
548 assertTrue(cim.unregisterContextInfoRetriever(RETRIEVER_NAME_1));
549 }
550
551 /**
552 * Test with a filter expression and an enabled context. The filter however
553 * should match the events, so events with the context info should be
554 * present in the resulting trace.
555 */
556 @Test
557 public void testContextFilterExpressionEnabledMatching() {
558 assertTrue(cim.registerContextInfoRetriever(RETRIEVER_NAME_1, ContextInfoRetrieverStubs.STRING_RETRIEVER));
559
560 assertTrue(session.enableEvent(EVENT_NAME, null, false,
d838d835 561 "$app." + RETRIEVER_NAME_1 + ':' + CONTEXT_NAME + "==\"" + ContextInfoRetrieverStubs.STRING_VALUE + '\"'));
8c202b54
AM
562
563 assertTrue(session.enableAppContext(RETRIEVER_NAME_1, CONTEXT_NAME));
564 assertTrue(session.start());
565 sendEventsToLoggers();
566 assertTrue(session.stop());
567
568 List<String> output = session.view();
569 assertNotNull(output);
570 assertFalse(output.isEmpty());
571
572 testContextPresentInTrace(output, RETRIEVER_NAME_1, CONTEXT_NAME,
fd2b8059 573 "{ \"" + ContextInfoRetrieverStubs.STRING_VALUE + "\" } }");
8c202b54
AM
574
575 assertTrue(cim.unregisterContextInfoRetriever(RETRIEVER_NAME_1));
576 }
73fb6785 577}
This page took 0.044921 seconds and 4 git commands to generate.