EQL: Refactor testing infrastructure (#62928)

Extract reusable methods inside QL TestUtils
Rename abstract base classes for clarity
Clean-up EQL DataLoader

(cherry picked from commit 48db3f285aa8976ead5a9f5d071a9c1046d7bd31)
This commit is contained in:
Costin Leau 2020-09-28 14:04:05 +03:00 committed by Costin Leau
parent 25106ba58f
commit ef7a6ce4b2
24 changed files with 523 additions and 267 deletions

View File

@ -6,12 +6,10 @@
package org.elasticsearch.test.eql; package org.elasticsearch.test.eql;
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import org.elasticsearch.Build;
import org.elasticsearch.client.EqlClient; import org.elasticsearch.client.EqlClient;
import org.elasticsearch.client.Request; import org.elasticsearch.client.Request;
import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.ResponseException;
import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.eql.EqlSearchRequest; import org.elasticsearch.client.eql.EqlSearchRequest;
import org.elasticsearch.client.eql.EqlSearchResponse; import org.elasticsearch.client.eql.EqlSearchResponse;
@ -21,76 +19,49 @@ import org.elasticsearch.client.eql.EqlSearchResponse.Sequence;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.logging.LoggerMessageFormat; import org.elasticsearch.common.logging.LoggerMessageFormat;
import org.elasticsearch.test.rest.ESRestTestCase; import org.elasticsearch.test.rest.ESRestTestCase;
import org.junit.After; import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
import static org.elasticsearch.test.eql.DataLoader.testIndexName;
public abstract class CommonEqlActionTestCase extends ESRestTestCase { public abstract class BaseEqlSpecTestCase extends ESRestTestCase {
protected static final String PARAM_FORMATTING = "%1$s.test -> %2$s"; protected static final String PARAM_FORMATTING = "%1$s.test -> %2$s";
private static int counter = 0;
private RestHighLevelClient highLevelClient; private RestHighLevelClient highLevelClient;
@BeforeClass private final String index;
public static void checkForSnapshot() { private final String query;
assumeTrue("Only works on snapshot builds for now", Build.CURRENT.isSnapshot()); private final String name;
} private final long[] eventIds;
private final boolean caseSensitive;
@Before @Before
public void setup() throws Exception { private void setup() throws Exception {
if (client().performRequest(new Request("HEAD", "/" + testIndexName)).getStatusLine().getStatusCode() == 404) { if (client().performRequest(new Request("HEAD", "/" + index)).getStatusLine().getStatusCode() == 404) {
DataLoader.loadDatasetIntoEs(highLevelClient(), (t, u) -> createParser(t, u)); DataLoader.loadDatasetIntoEs(highLevelClient(), this::createParser);
} }
} }
@After @AfterClass
public void cleanup() throws Exception { public static void wipeTestData() throws IOException {
if (--counter == 0) { try {
deleteIndex(testIndexName); adminClient().performRequest(new Request("DELETE", "/*"));
} catch (ResponseException e) {
// 404 here just means we had no indexes
if (e.getResponse().getStatusLine().getStatusCode() != 404) {
throw e;
}
} }
} }
@ParametersFactory(shuffle = false, argumentFormatting = PARAM_FORMATTING) protected static List<Object[]> asArray(List<EqlSpec> specs) {
public static List<Object[]> readTestSpecs() throws Exception {
// Load EQL validation specs
Set<String> uniqueTestNames = new HashSet<>();
List<EqlSpec> specs = EqlSpecLoader.load("/test_queries.toml", true, uniqueTestNames);
specs.addAll(EqlSpecLoader.load("/additional_test_queries.toml", true, uniqueTestNames));
List<EqlSpec> unsupportedSpecs = EqlSpecLoader.load("/test_queries_unsupported.toml", false, uniqueTestNames);
// Validate only currently supported specs
List<EqlSpec> filteredSpecs = new ArrayList<>();
for (EqlSpec spec : specs) {
boolean supported = true;
// Check if spec is supported, simple iteration, cause the list is short.
for (EqlSpec unSpec : unsupportedSpecs) {
if (spec.equals(unSpec)) {
supported = false;
break;
}
}
if (supported) {
filteredSpecs.add(spec);
}
}
counter = specs.size();
return asArray(filteredSpecs);
}
private static List<Object[]> asArray(List<EqlSpec> specs) {
int counter = 0; int counter = 0;
List<Object[]> results = new ArrayList<>(); List<Object[]> results = new ArrayList<>();
@ -114,12 +85,9 @@ public abstract class CommonEqlActionTestCase extends ESRestTestCase {
return results; return results;
} }
private final String query; BaseEqlSpecTestCase(String index, String query, String name, long[] eventIds, boolean caseSensitive) {
private final String name; this.index = index;
private final long[] eventIds;
private final boolean caseSensitive;
public CommonEqlActionTestCase(String query, String name, long[] eventIds, boolean caseSensitive) {
this.query = query; this.query = query;
this.name = name; this.name = name;
this.eventIds = eventIds; this.eventIds = eventIds;
@ -127,7 +95,7 @@ public abstract class CommonEqlActionTestCase extends ESRestTestCase {
} }
public void test() throws Exception { public void test() throws Exception {
assertResponse(runQuery(testIndexName, query, caseSensitive)); assertResponse(runQuery(index, query, caseSensitive));
} }
protected void assertResponse(EqlSearchResponse response) { protected void assertResponse(EqlSearchResponse response) {
@ -150,15 +118,20 @@ public abstract class CommonEqlActionTestCase extends ESRestTestCase {
// some queries return more than 10 results // some queries return more than 10 results
request.size(50); request.size(50);
request.fetchSize(randomIntBetween(2, 50)); request.fetchSize(randomIntBetween(2, 50));
return eqlClient().search(request, RequestOptions.DEFAULT); return runRequest(eqlClient(), request);
} }
private EqlClient eqlClient() { protected EqlSearchResponse runRequest(EqlClient eqlClient, EqlSearchRequest request) throws IOException {
return eqlClient.search(request, RequestOptions.DEFAULT);
}
protected EqlClient eqlClient() {
return highLevelClient().eql(); return highLevelClient().eql();
} }
protected void assertEvents(List<Event> events) { protected void assertEvents(List<Event> events) {
assertNotNull(events); assertNotNull(events);
logger.info("Events {}", events);
long[] expected = eventIds; long[] expected = eventIds;
long[] actual = extractIds(events); long[] actual = extractIds(events);
assertArrayEquals(LoggerMessageFormat.format(null, "unexpected result for spec[{}] [{}] -> {} vs {}", name, query, Arrays.toString( assertArrayEquals(LoggerMessageFormat.format(null, "unexpected result for spec[{}] [{}] -> {} vs {}", name, query, Arrays.toString(
@ -166,11 +139,13 @@ public abstract class CommonEqlActionTestCase extends ESRestTestCase {
expected, actual); expected, actual);
} }
private static long[] extractIds(List<Event> events) { @SuppressWarnings("unchecked")
private long[] extractIds(List<Event> events) {
final int len = events.size(); final int len = events.size();
final long ids[] = new long[len]; final long[] ids = new long[len];
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
ids[i] = ((Number) events.get(i).sourceAsMap().get("serial_event_id")).longValue(); Object field = events.get(i).sourceAsMap().get(sequenceField());
ids[i] = ((Number) field).longValue();
} }
return ids; return ids;
} }
@ -199,4 +174,8 @@ public abstract class CommonEqlActionTestCase extends ESRestTestCase {
// Need to preserve data between parameterized tests runs // Need to preserve data between parameterized tests runs
return true; return true;
} }
protected String sequenceField() {
return "sequence";
}
} }

View File

@ -24,12 +24,12 @@ import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.test.rest.ESRestTestCase; import org.elasticsearch.test.rest.ESRestTestCase;
import org.elasticsearch.xpack.ql.TestUtils;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -39,13 +39,20 @@ import java.util.Map.Entry;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
/**
* Loads EQL dataset into ES.
*
* Checks for predefined indices:
* - endgame-140 - for existing data
* - extra - additional data
*
* While the loader could be made generic, the queries are bound to each index and generalizing that would make things way too complicated.
*/
public class DataLoader { public class DataLoader {
public static final String TEST_INDEX = "endgame-140";
public static final String TEST_EXTRA_INDEX = "extra";
private static final String TEST_DATA = "/test_data.json";
private static final String MAPPING = "/mapping-default.json";
private static final Map<String, String[]> replacementPatterns = Collections.unmodifiableMap(getReplacementPatterns()); private static final Map<String, String[]> replacementPatterns = Collections.unmodifiableMap(getReplacementPatterns());
static final String indexPrefix = "endgame";
public static final String testIndexName = indexPrefix + "-1.4.0";
private static final long FILETIME_EPOCH_DIFF = 11644473600000L; private static final long FILETIME_EPOCH_DIFF = 11644473600000L;
private static final long FILETIME_ONE_MILLISECOND = 10 * 1000; private static final long FILETIME_ONE_MILLISECOND = 10 * 1000;
@ -63,26 +70,52 @@ public class DataLoader {
ignore -> { ignore -> {
}, },
Collections.emptyList()) { Collections.emptyList()) {
}, (t, u) -> createParser(t, u)); }, DataLoader::createParser);
} }
} }
public static void loadDatasetIntoEs(RestHighLevelClient client, public static void loadDatasetIntoEs(RestHighLevelClient client,
CheckedBiFunction<XContent, InputStream, XContentParser, IOException> p) throws IOException { CheckedBiFunction<XContent, InputStream, XContentParser, IOException> p) throws IOException {
createTestIndex(client); //
loadData(client, p); // Main Index
//
load(client, TEST_INDEX, true, p);
//
// Aux Index
//
load(client, TEST_EXTRA_INDEX, false, p);
} }
private static void createTestIndex(RestHighLevelClient client) throws IOException { private static void load(RestHighLevelClient client, String indexName, boolean winFileTime,
CreateIndexRequest request = new CreateIndexRequest(testIndexName).mapping(getMapping(MAPPING), XContentType.JSON); CheckedBiFunction<XContent, InputStream, XContentParser, IOException> p) throws IOException {
String name = "/data/" + indexName + ".mapping";
URL mapping = DataLoader.class.getResource(name);
if (mapping == null) {
throw new IllegalArgumentException("Cannot find resource " + name);
}
name = "/data/" + indexName + ".data";
URL data = DataLoader.class.getResource(name);
if (data == null) {
throw new IllegalArgumentException("Cannot find resource " + name);
}
createTestIndex(client, indexName, readMapping(mapping));
loadData(client, indexName, winFileTime, data, p);
}
private static void createTestIndex(RestHighLevelClient client, String indexName, String mapping) throws IOException {
CreateIndexRequest request = new CreateIndexRequest(indexName);
if (mapping != null) {
request.mapping(mapping, XContentType.JSON);
}
client.indices().create(request, RequestOptions.DEFAULT); client.indices().create(request, RequestOptions.DEFAULT);
} }
private static String getMapping(String mappingPath) throws IOException { /**
try (BufferedReader reader = new BufferedReader( * Reads the mapping file, ignoring comments and replacing placeholders for random types.
new InputStreamReader(DataLoader.class.getResourceAsStream(mappingPath), StandardCharsets.UTF_8))) */
{ private static String readMapping(URL resource) throws IOException {
try (BufferedReader reader = TestUtils.reader(resource)) {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
String line; String line;
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
@ -98,27 +131,30 @@ public class DataLoader {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private static void loadData(RestHighLevelClient client, CheckedBiFunction<XContent, InputStream, XContentParser, IOException> p) private static void loadData(RestHighLevelClient client, String indexName, boolean winfileTime, URL resource,
CheckedBiFunction<XContent, InputStream, XContentParser, IOException> p)
throws IOException { throws IOException {
BulkRequest bulk = new BulkRequest(); BulkRequest bulk = new BulkRequest();
bulk.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); bulk.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
try (XContentParser parser = p.apply(JsonXContent.jsonXContent, DataLoader.class.getResourceAsStream(TEST_DATA))) { try (XContentParser parser = p.apply(JsonXContent.jsonXContent, TestUtils.inputStream(resource))) {
List<Object> list = parser.list(); List<Object> list = parser.list();
for (Object item : list) { for (Object item : list) {
assertThat(item, instanceOf(Map.class)); assertThat(item, instanceOf(Map.class));
Map<String, Object> entry = (Map<String, Object>) item; Map<String, Object> entry = (Map<String, Object>) item;
if (winfileTime) {
transformDataset(entry); transformDataset(entry);
bulk.add(new IndexRequest(testIndexName).source(entry, XContentType.JSON)); }
bulk.add(new IndexRequest(indexName).source(entry, XContentType.JSON));
} }
} }
if (bulk.numberOfActions() > 0) { if (bulk.numberOfActions() > 0) {
BulkResponse bulkResponse = client.bulk(bulk, RequestOptions.DEFAULT); BulkResponse bulkResponse = client.bulk(bulk, RequestOptions.DEFAULT);
if (bulkResponse.hasFailures()) { if (bulkResponse.hasFailures()) {
LogManager.getLogger(DataLoader.class).info("Data FAILED loading"); LogManager.getLogger(DataLoader.class).info("Data loading FAILED");
} else { } else {
LogManager.getLogger(DataLoader.class).info("Data loaded"); LogManager.getLogger(DataLoader.class).info("Data loading OK");
} }
} }
} }

View File

@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.test.eql;
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import java.util.HashSet;
import java.util.List;
import static org.elasticsearch.test.eql.DataLoader.TEST_EXTRA_INDEX;
public abstract class EqlExtraSpecTestCase extends BaseEqlSpecTestCase {
@ParametersFactory(shuffle = false, argumentFormatting = PARAM_FORMATTING)
public static List<Object[]> readTestSpecs() throws Exception {
return asArray(EqlSpecLoader.load("/test_extra.toml", true, new HashSet<>()));
}
public EqlExtraSpecTestCase(String query, String name, long[] eventIds, boolean caseSensitive) {
super(TEST_EXTRA_INDEX, query, name, eventIds, caseSensitive);
}
}

View File

@ -26,7 +26,7 @@ import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
public abstract class CommonEqlRestTestCase extends ESRestTestCase { public abstract class EqlRestTestCase extends ESRestTestCase {
private static final String defaultValidationIndexName = "eql_search_validation_test"; private static final String defaultValidationIndexName = "eql_search_validation_test";
private static final String validQuery = "process where user = \\\"SYSTEM\\\""; private static final String validQuery = "process where user = \\\"SYSTEM\\\"";

View File

@ -20,6 +20,9 @@ import java.util.Set;
public class EqlSpecLoader { public class EqlSpecLoader {
public static List<EqlSpec> load(String path, boolean supported, Set<String> uniqueTestNames) throws Exception { public static List<EqlSpec> load(String path, boolean supported, Set<String> uniqueTestNames) throws Exception {
try (InputStream is = EqlSpecLoader.class.getResourceAsStream(path)) { try (InputStream is = EqlSpecLoader.class.getResourceAsStream(path)) {
if (is == null) {
throw new IllegalAccessException("Cannot find classpath resource " + path);
}
return readFromStream(is, supported, uniqueTestNames); return readFromStream(is, supported, uniqueTestNames);
} }
} }

View File

@ -0,0 +1,52 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.test.eql;
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.elasticsearch.test.eql.DataLoader.TEST_INDEX;
public abstract class EqlSpecTestCase extends BaseEqlSpecTestCase {
@ParametersFactory(shuffle = false, argumentFormatting = PARAM_FORMATTING)
public static List<Object[]> readTestSpecs() throws Exception {
// Load EQL validation specs
Set<String> uniqueTestNames = new HashSet<>();
List<EqlSpec> specs = EqlSpecLoader.load("/test_queries.toml", true, uniqueTestNames);
specs.addAll(EqlSpecLoader.load("/additional_test_queries.toml", true, uniqueTestNames));
List<EqlSpec> unsupportedSpecs = EqlSpecLoader.load("/test_queries_unsupported.toml", false, uniqueTestNames);
// Validate only currently supported specs
List<EqlSpec> filteredSpecs = new ArrayList<>();
for (EqlSpec spec : specs) {
boolean supported = true;
// Check if spec is supported, simple iteration, cause the list is short.
for (EqlSpec unSpec : unsupportedSpecs) {
if (spec.equals(unSpec)) {
supported = false;
break;
}
}
if (supported) {
filteredSpecs.add(spec);
}
}
return asArray(filteredSpecs);
}
public EqlSpecTestCase(String query, String name, long[] eventIds, boolean caseSensitive) {
super(TEST_INDEX, query, name, eventIds, caseSensitive);
}
}

View File

@ -6,7 +6,6 @@
package org.elasticsearch.test.eql.stats; package org.elasticsearch.test.eql.stats;
import org.elasticsearch.Build;
import org.elasticsearch.client.Request; import org.elasticsearch.client.Request;
import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentHelper;
@ -14,7 +13,6 @@ import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.test.eql.DataLoader; import org.elasticsearch.test.eql.DataLoader;
import org.elasticsearch.test.rest.ESRestTestCase; import org.elasticsearch.test.rest.ESRestTestCase;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -27,23 +25,18 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import static java.util.Collections.unmodifiableSet; import static java.util.Collections.unmodifiableSet;
import static org.elasticsearch.test.eql.DataLoader.testIndexName;
/** /**
* Tests a random number of queries that increase various (most of the times, one query will "touch" multiple metrics values) metrics. * Tests a random number of queries that increase various (most of the times, one query will "touch" multiple metrics values) metrics.
*/ */
public abstract class RestEqlUsageTestCase extends ESRestTestCase { public abstract class EqlUsageRestTestCase extends ESRestTestCase {
private RestHighLevelClient highLevelClient; private RestHighLevelClient highLevelClient;
private Map<String, Integer> baseMetrics = new HashMap<String, Integer>(); private Map<String, Integer> baseMetrics = new HashMap<String, Integer>();
private Integer baseAllTotalQueries = 0; private Integer baseAllTotalQueries = 0;
private Integer baseAllFailedQueries = 0; private Integer baseAllFailedQueries = 0;
@BeforeClass
public static void checkForSnapshot() {
assumeTrue("Only works on snapshot builds for now", Build.CURRENT.isSnapshot());
}
/** /**
* This method gets the metrics' values before the test runs, in case these values * This method gets the metrics' values before the test runs, in case these values
* were changed by other tests running in the same REST test cluster. The test itself * were changed by other tests running in the same REST test cluster. The test itself
@ -123,8 +116,8 @@ public abstract class RestEqlUsageTestCase extends ESRestTestCase {
// create the index and load the data, if the index doesn't exist // create the index and load the data, if the index doesn't exist
// it doesn't matter if the index is already there (probably created by another test); _if_ its mapping is the expected one // it doesn't matter if the index is already there (probably created by another test); _if_ its mapping is the expected one
// it should be enough // it should be enough
if (client().performRequest(new Request("HEAD", "/" + testIndexName)).getStatusLine().getStatusCode() == 404) { if (client().performRequest(new Request("HEAD", "/" + DataLoader.TEST_INDEX)).getStatusLine().getStatusCode() == 404) {
DataLoader.loadDatasetIntoEs(highLevelClient(), (t, u) -> createParser(t, u)); DataLoader.loadDatasetIntoEs(highLevelClient(), this::createParser);
} }
// //
@ -309,8 +302,12 @@ public abstract class RestEqlUsageTestCase extends ESRestTestCase {
} }
private void runEql(String eql) throws IOException { private void runEql(String eql) throws IOException {
Request request = new Request("POST", DataLoader.testIndexName + "/_eql/search"); Request request = new Request("POST", DataLoader.TEST_INDEX + "/_eql/search");
request.setJsonEntity("{\"query\":\"" + eql +"\"}"); request.setJsonEntity("{\"query\":\"" + eql +"\"}");
runRequest(request);
}
protected void runRequest(Request request) throws IOException {
client().performRequest(request); client().performRequest(request);
} }

View File

@ -0,0 +1,38 @@
[
{
"@timestamp": "1",
"event_type": "REQUEST",
"transID": 1234,
"sequence": 1
},
{
"@timestamp": "2",
"event_type": "ERROR",
"transID": 1234,
"sequence": 2
},
{
"@timestamp": "3",
"event_type": "STAT",
"transID": 1234,
"sequence": 3
},
{
"@timestamp": "10",
"event_type": "REQUEST",
"transID": 1235,
"sequence": 1
},
{
"@timestamp": "11",
"event_type": "ERROR",
"transID": 1235,
"sequence": 2
},
{
"@timestamp": "11",
"event_type": "STAT",
"transID": 1235,
"sequence": 3
}
]

View File

@ -0,0 +1,25 @@
{
"properties": {
"@timestamp": {
"type": "date"
},
"event_type": {
"type": "[runtime_random_keyword_type]"
},
"sequence": {
"type": "long"
},
"event": {
"properties": {
"category": {
"type": "alias",
"path": "event_type"
},
"sequence": {
"type": "alias",
"path": "sequence"
}
}
}
}
}

View File

@ -0,0 +1,29 @@
[[queries]]
name = "basic"
query = '''
sequence by transID
[ REQUEST where true ]
[ ERROR where true ]
[ STAT where true ]
'''
expected_event_ids = [1,2,3]
[[queries]]
name = "basicWithFilter"
query = '''
sequence by transID
[ REQUEST where transID == 1234 ]
[ ERROR where true ]
[ STAT where true ]
'''
expected_event_ids = [1,2,3]
[[queries]]
name = "basicWithFilters"
query = '''
sequence by transID
[ REQUEST where transID == 1234 ]
[ ERROR where transID == 1234 ]
[ STAT where transID == 1234 ]
'''
expected_event_ids = [1,2,3]

View File

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.eql;
import org.elasticsearch.test.eql.EqlExtraSpecTestCase;
import org.elasticsearch.test.junit.annotations.TestLogging;
@TestLogging(value = "org.elasticsearch.xpack.eql:TRACE", reason = "results logging")
public class EqlExtraIT extends EqlExtraSpecTestCase {
public EqlExtraIT(String query, String name, long[] eventIds, boolean caseSensitive) {
super(query, name, eventIds, caseSensitive);
}
}

View File

@ -6,7 +6,7 @@
package org.elasticsearch.xpack.eql; package org.elasticsearch.xpack.eql;
import org.elasticsearch.test.eql.CommonEqlRestTestCase; import org.elasticsearch.test.eql.EqlRestTestCase;
public class EqlIT extends CommonEqlRestTestCase { public class EqlRestIT extends EqlRestTestCase {
} }

View File

@ -6,12 +6,16 @@
package org.elasticsearch.xpack.eql; package org.elasticsearch.xpack.eql;
import org.elasticsearch.test.eql.CommonEqlActionTestCase; import org.elasticsearch.test.eql.EqlSpecTestCase;
public class EqlActionIT extends CommonEqlActionTestCase { public class EqlSpecIT extends EqlSpecTestCase {
public EqlActionIT(String query, String name, long[] eventIds, boolean caseSensitive) { public EqlSpecIT(String query, String name, long[] eventIds, boolean caseSensitive) {
super(query, name, eventIds, caseSensitive); super(query, name, eventIds, caseSensitive);
} }
@Override
protected String sequenceField() {
return "serial_event_id";
}
} }

View File

@ -6,8 +6,8 @@
package org.elasticsearch.xpack.eql; package org.elasticsearch.xpack.eql;
import org.elasticsearch.test.eql.stats.RestEqlUsageTestCase; import org.elasticsearch.test.eql.stats.EqlUsageRestTestCase;
public class EqlStatsIT extends RestEqlUsageTestCase { public class EqlStatsIT extends EqlUsageRestTestCase {
} }

View File

@ -7,16 +7,12 @@
package org.elasticsearch.xpack.eql; package org.elasticsearch.xpack.eql;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import org.elasticsearch.Build;
import org.elasticsearch.client.Request; import org.elasticsearch.client.Request;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.Response; import org.elasticsearch.client.Response;
import org.elasticsearch.client.ResponseException; import org.elasticsearch.client.ResponseException;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.common.xcontent.json.JsonXContent;
@ -24,33 +20,24 @@ import org.elasticsearch.test.rest.ESRestTestCase;
import org.elasticsearch.xpack.core.XPackPlugin; import org.elasticsearch.xpack.core.XPackPlugin;
import org.elasticsearch.xpack.core.async.AsyncExecutionId; import org.elasticsearch.xpack.core.async.AsyncExecutionId;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.xpack.core.security.authc.AuthenticationServiceField.RUN_AS_USER_HEADER; import static org.elasticsearch.xpack.eql.SecurityUtils.secureClientSettings;
import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; import static org.elasticsearch.xpack.eql.SecurityUtils.setRunAsHeader;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
public class AsyncEqlSecurityIT extends ESRestTestCase { public class AsyncEqlSecurityIT extends ESRestTestCase {
@BeforeClass
public static void checkForSnapshot() {
assumeTrue("Only works on snapshot builds for now", Build.CURRENT.isSnapshot());
}
/** /**
* All tests run as a superuser but use <code>es-security-runas-user</code> to become a less privileged user. * All tests run as a superuser but use <code>es-security-runas-user</code> to become a less privileged user.
*/ */
@Override @Override
protected Settings restClientSettings() { protected Settings restClientSettings() {
String token = basicAuthHeaderValue("test-admin", new SecureString("x-pack-test-password".toCharArray())); return secureClientSettings();
return Settings.builder()
.put(ThreadContext.PREFIX + ".Authorization", token)
.build();
} }
@Before @Before
@ -166,11 +153,4 @@ public class AsyncEqlSecurityIT extends ESRestTestCase {
static Map<String, Object> toMap(String response) { static Map<String, Object> toMap(String response) {
return XContentHelper.convertToMap(JsonXContent.jsonXContent, response, false); return XContentHelper.convertToMap(JsonXContent.jsonXContent, response, false);
} }
static void setRunAsHeader(Request request, String user) {
final RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
builder.addHeader(RUN_AS_USER_HEADER, user);
request.setOptions(builder);
}
} }

View File

@ -0,0 +1,32 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.eql;
import org.elasticsearch.client.Request;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.eql.stats.EqlUsageRestTestCase;
import java.io.IOException;
import static org.elasticsearch.xpack.eql.SecurityUtils.secureClientSettings;
public class EqlStatsIT extends EqlUsageRestTestCase {
/**
* All tests run as a superuser but use <code>es-security-runas-user</code> to become a less privileged user.
*/
@Override
protected Settings restClientSettings() {
return secureClientSettings();
}
@Override
protected void runRequest(Request request) throws IOException {
SecurityUtils.setRunAsHeader(request,"test-admin");
super.runRequest(request);
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.eql;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import static org.elasticsearch.xpack.core.security.authc.AuthenticationServiceField.RUN_AS_USER_HEADER;
import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
public class SecurityUtils {
static Settings secureClientSettings() {
String token = basicAuthHeaderValue("test-admin", new SecureString("x-pack-test-password".toCharArray()));
return Settings.builder()
.put(ThreadContext.PREFIX + ".Authorization", token)
.build();
}
static void setRunAsHeader(Request request, String user) {
final RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
builder.addHeader(RUN_AS_USER_HEADER, user);
request.setOptions(builder);
}
static void setUserRole(Request request) {
setRunAsHeader(request, "user1");
}
}

View File

@ -9,6 +9,10 @@ package org.elasticsearch.xpack.ql;
import org.elasticsearch.client.Request; import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response; import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClient;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.xpack.ql.expression.Expression; import org.elasticsearch.xpack.ql.expression.Expression;
@ -24,11 +28,28 @@ import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.NullE
import org.elasticsearch.xpack.ql.session.Configuration; import org.elasticsearch.xpack.ql.session.Configuration;
import org.elasticsearch.xpack.ql.tree.Source; import org.elasticsearch.xpack.ql.tree.Source;
import org.elasticsearch.xpack.ql.type.DataTypes; import org.elasticsearch.xpack.ql.type.DataTypes;
import org.elasticsearch.xpack.ql.util.StringUtils;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.jar.JarInputStream;
import java.util.zip.ZipEntry;
import static org.elasticsearch.test.ESTestCase.randomAlphaOfLength; import static org.elasticsearch.test.ESTestCase.randomAlphaOfLength;
import static org.elasticsearch.test.ESTestCase.randomZone; import static org.elasticsearch.test.ESTestCase.randomZone;
@ -131,4 +152,96 @@ public final class TestUtils {
stats = (Map<String, Object>) stats.get("search"); stats = (Map<String, Object>) stats.get("search");
return (Integer) stats.get("open_contexts"); return (Integer) stats.get("open_contexts");
} }
//
// Classpath
//
/**
* Returns the classpath resources matching a simple pattern ("*.csv").
* It supports folders separated by "/" (e.g. "/some/folder/*.txt").
*
* Currently able to resolve resources inside the classpath either from:
* folders in the file-system (typically IDEs) or
* inside jars (gradle).
*/
@SuppressForbidden(reason = "classpath discovery")
public static List<URL> classpathResources(String pattern) throws IOException {
while (pattern.startsWith("/")) {
pattern = pattern.substring(1);
}
Tuple<String, String> split = pathAndName(pattern);
// the root folder searched inside the classpath - default is the root classpath
// default file match
final String root = split.v1();
final String filePattern = split.v2();
String[] resources = System.getProperty("java.class.path").split(System.getProperty("path.separator"));
List<URL> matches = new ArrayList<>();
for (String resource : resources) {
Path path = PathUtils.get(resource);
// check whether we're dealing with a jar
// Java 7 java.nio.fileFileSystem can be used on top of ZIPs/JARs but consumes more memory
// hence the use of the JAR API
if (path.toString().endsWith(".jar")) {
try (JarInputStream jar = jarInputStream(path.toUri().toURL())) {
ZipEntry entry = null;
while ((entry = jar.getNextEntry()) != null) {
String name = entry.getName();
Tuple<String, String> entrySplit = pathAndName(name);
if (root.equals(entrySplit.v1()) && Regex.simpleMatch(filePattern, entrySplit.v2())) {
matches.add(new URL("jar:" + path.toUri() + "!/" + name));
}
}
}
}
// normal file access
else if (Files.isDirectory(path)) {
Files.walkFileTree(path, EnumSet.allOf(FileVisitOption.class), 1, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (Regex.simpleMatch(filePattern, file.toString())) {
matches.add(file.toUri().toURL());
}
return FileVisitResult.CONTINUE;
}
});
}
}
return matches;
}
@SuppressForbidden(reason = "need to open stream")
public static InputStream inputStream(URL resource) throws IOException {
URLConnection con = resource.openConnection();
// do not to cache files (to avoid keeping file handles around)
con.setUseCaches(false);
return con.getInputStream();
}
@SuppressForbidden(reason = "need to open jar")
public static JarInputStream jarInputStream(URL resource) throws IOException {
return new JarInputStream(inputStream(resource));
}
public static BufferedReader reader(URL resource) throws IOException {
return new BufferedReader(new InputStreamReader(inputStream(resource), StandardCharsets.UTF_8));
}
public static Tuple<String, String> pathAndName(String string) {
String folder = StringUtils.EMPTY;
String file = string;
int lastIndexOf = string.lastIndexOf("/");
if (lastIndexOf > 0) {
folder = string.substring(0, lastIndexOf - 1);
if (lastIndexOf + 1 < string.length()) {
file = string.substring(lastIndexOf + 1);
}
}
return new Tuple<>(folder, file);
}
} }

View File

@ -8,6 +8,7 @@ package org.elasticsearch.xpack.sql.qa.jdbc;
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.elasticsearch.xpack.ql.TestUtils;
import org.elasticsearch.xpack.sql.qa.jdbc.CsvTestUtils.CsvTestCase; import org.elasticsearch.xpack.sql.qa.jdbc.CsvTestUtils.CsvTestCase;
import java.net.URL; import java.net.URL;
@ -29,7 +30,7 @@ public abstract class CsvSpecTestCase extends SpecBaseIntegrationTestCase {
@ParametersFactory(argumentFormatting = PARAM_FORMATTING) @ParametersFactory(argumentFormatting = PARAM_FORMATTING)
public static List<Object[]> readScriptSpec() throws Exception { public static List<Object[]> readScriptSpec() throws Exception {
List<URL> urls = JdbcTestUtils.classpathResources("/*.csv-spec"); List<URL> urls = TestUtils.classpathResources("/*.csv-spec");
assertTrue("Not enough specs found (" + urls.size() + ") " + urls.toString(), urls.size() >= 23); assertTrue("Not enough specs found (" + urls.size() + ") " + urls.toString(), urls.size() >= 23);
return readScriptSpec(urls, specParser()); return readScriptSpec(urls, specParser());
} }

View File

@ -6,34 +6,14 @@
package org.elasticsearch.xpack.sql.qa.jdbc; package org.elasticsearch.xpack.sql.qa.jdbc;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.xpack.sql.action.BasicFormatter; import org.elasticsearch.xpack.sql.action.BasicFormatter;
import org.elasticsearch.xpack.sql.proto.ColumnInfo; import org.elasticsearch.xpack.sql.proto.ColumnInfo;
import org.elasticsearch.xpack.sql.proto.StringUtils;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.ResultSetMetaData; import java.sql.ResultSetMetaData;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.jar.JarInputStream;
import java.util.zip.ZipEntry;
import static org.elasticsearch.xpack.sql.action.BasicFormatter.FormatOption.CLI; import static org.elasticsearch.xpack.sql.action.BasicFormatter.FormatOption.CLI;
@ -146,87 +126,4 @@ final class JdbcTestUtils {
BasicFormatter formatter = new BasicFormatter(cols, data, CLI); BasicFormatter formatter = new BasicFormatter(cols, data, CLI);
logger.info("\n" + formatter.formatWithHeader(cols, data)); logger.info("\n" + formatter.formatWithHeader(cols, data));
} }
static String of(long millis, String zoneId) {
return StringUtils.toString(ZonedDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneId.of(zoneId)));
}
/**
* Returns the classpath resources matching a simple pattern ("*.csv").
* It supports folders separated by "/" (e.g. "/some/folder/*.txt").
*
* Currently able to resolve resources inside the classpath either from:
* folders in the file-system (typically IDEs) or
* inside jars (gradle).
*/
static List<URL> classpathResources(String pattern) throws Exception {
while (pattern.startsWith("/")) {
pattern = pattern.substring(1);
}
Tuple<String, String> split = pathAndName(pattern);
// the root folder searched inside the classpath - default is the root classpath
// default file match
final String root = split.v1();
final String filePattern = split.v2();
String[] resources = System.getProperty("java.class.path").split(System.getProperty("path.separator"));
List<URL> matches = new ArrayList<>();
for (String resource : resources) {
Path path = PathUtils.get(resource);
// check whether we're dealing with a jar
// Java 7 java.nio.fileFileSystem can be used on top of ZIPs/JARs but consumes more memory
// hence the use of the JAR API
if (path.toString().endsWith(".jar")) {
try (JarInputStream jar = getJarStream(path.toUri().toURL())) {
ZipEntry entry = null;
while ((entry = jar.getNextEntry()) != null) {
String name = entry.getName();
Tuple<String, String> entrySplit = pathAndName(name);
if (root.equals(entrySplit.v1()) && Regex.simpleMatch(filePattern, entrySplit.v2())) {
matches.add(new URL("jar:" + path.toUri() + "!/" + name));
}
}
}
}
// normal file access
else if (Files.isDirectory(path)) {
Files.walkFileTree(path, EnumSet.allOf(FileVisitOption.class), 1, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (Regex.simpleMatch(filePattern, file.toString())) {
matches.add(file.toUri().toURL());
}
return FileVisitResult.CONTINUE;
}
});
}
}
return matches;
}
@SuppressForbidden(reason = "need to open jar")
private static JarInputStream getJarStream(URL resource) throws IOException {
URLConnection con = resource.openConnection();
// do not to cache files (to avoid keeping file handles around)
con.setUseCaches(false);
return new JarInputStream(con.getInputStream());
}
static Tuple<String, String> pathAndName(String string) {
String folder = StringUtils.EMPTY;
String file = string;
int lastIndexOf = string.lastIndexOf("/");
if (lastIndexOf > 0) {
folder = string.substring(0, lastIndexOf - 1);
if (lastIndexOf + 1 < string.length()) {
file = string.substring(lastIndexOf + 1);
}
}
return new Tuple<>(folder, file);
}
} }

View File

@ -10,17 +10,13 @@ import org.elasticsearch.client.Request;
import org.elasticsearch.client.ResponseException; import org.elasticsearch.client.ResponseException;
import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClient;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.SuppressForbidden; import org.elasticsearch.xpack.ql.TestUtils;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL; import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.sql.Connection; import java.sql.Connection;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
@ -33,6 +29,7 @@ import java.util.Objects;
import java.util.Properties; import java.util.Properties;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static org.elasticsearch.xpack.ql.TestUtils.pathAndName;
import static org.elasticsearch.xpack.sql.qa.jdbc.JdbcTestUtils.JDBC_TIMEZONE; import static org.elasticsearch.xpack.sql.qa.jdbc.JdbcTestUtils.JDBC_TIMEZONE;
/** /**
@ -168,14 +165,14 @@ public abstract class SpecBaseIntegrationTestCase extends JdbcIntegrationTestCas
} }
private static List<Object[]> readURLSpec(URL source, Parser parser) throws Exception { private static List<Object[]> readURLSpec(URL source, Parser parser) throws Exception {
String fileName = JdbcTestUtils.pathAndName(source.getFile()).v2(); String fileName = pathAndName(source.getFile()).v2();
String groupName = fileName.substring(0, fileName.lastIndexOf(".")); String groupName = fileName.substring(0, fileName.lastIndexOf("."));
Map<String, Integer> testNames = new LinkedHashMap<>(); Map<String, Integer> testNames = new LinkedHashMap<>();
List<Object[]> testCases = new ArrayList<>(); List<Object[]> testCases = new ArrayList<>();
String testName = null; String testName = null;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(readFromJarUrl(source), StandardCharsets.UTF_8))) { try (BufferedReader reader = TestUtils.reader(source)) {
String line; String line;
int lineNumber = 1; int lineNumber = 1;
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
@ -221,12 +218,4 @@ public abstract class SpecBaseIntegrationTestCase extends JdbcIntegrationTestCas
public interface Parser { public interface Parser {
Object parse(String line); Object parse(String line);
} }
@SuppressForbidden(reason = "test reads from jar")
public static InputStream readFromJarUrl(URL source) throws IOException {
URLConnection con = source.openConnection();
// do not to cache files (to avoid keeping file handles around)
con.setUseCaches(false);
return con.getInputStream();
}
} }

View File

@ -6,7 +6,6 @@
package org.elasticsearch.xpack.sql.qa.jdbc; package org.elasticsearch.xpack.sql.qa.jdbc;
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import org.junit.Assume; import org.junit.Assume;
import org.junit.ClassRule; import org.junit.ClassRule;
@ -20,6 +19,8 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.TimeZone; import java.util.TimeZone;
import static org.elasticsearch.xpack.ql.TestUtils.classpathResources;
/** /**
* Tests comparing sql queries executed against our jdbc client * Tests comparing sql queries executed against our jdbc client
* with those executed against H2's jdbc client. * with those executed against H2's jdbc client.
@ -33,7 +34,7 @@ public abstract class SqlSpecTestCase extends SpecBaseIntegrationTestCase {
@ParametersFactory(argumentFormatting = PARAM_FORMATTING) @ParametersFactory(argumentFormatting = PARAM_FORMATTING)
public static List<Object[]> readScriptSpec() throws Exception { public static List<Object[]> readScriptSpec() throws Exception {
List<URL> urls = JdbcTestUtils.classpathResources("/*.sql-spec"); List<URL> urls = classpathResources("/*.sql-spec");
assertTrue("Not enough specs found " + urls.toString(), urls.size() > 10); assertTrue("Not enough specs found " + urls.toString(), urls.size() > 10);
return readScriptSpec(urls, specParser()); return readScriptSpec(urls, specParser());
} }