Skip to content

Add ability to filter tests #360

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
Expand Down Expand Up @@ -214,8 +215,19 @@ void execute() {
// Generate protocol tests IFF found in the model.
if (protocolGenerator != null) {
ShapeId protocol = protocolGenerator.getProtocol();
new HttpProtocolTestGenerator(
settings, model, protocol, symbolProvider, writers, protocolGenerator).run();
ProtocolGenerator.GenerationContext context = new ProtocolGenerator.GenerationContext();
context.setProtocolName(protocolGenerator.getName());
context.setIntegrations(integrations);
context.setModel(model);
context.setService(service);
context.setSettings(settings);
context.setSymbolProvider(symbolProvider);
String baseName = protocol.getName().toLowerCase(Locale.US)
.replace("-", "_")
.replace(".", "_");
String protocolTestFileName = String.format("tests/functional/%s.spec.ts", baseName);
context.setDeferredWriter(() -> writers.checkoutFileWriter(protocolTestFileName));
protocolGenerator.generateProtocolTests(context);
}

// Write each pending writer.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import software.amazon.smithy.protocoltests.traits.HttpResponseTestCase;
import software.amazon.smithy.protocoltests.traits.HttpResponseTestsTrait;
import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator;
import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator.GenerationContext;
import software.amazon.smithy.utils.IoUtils;
import software.amazon.smithy.utils.MapUtils;
import software.amazon.smithy.utils.Pair;
Expand All @@ -81,7 +82,7 @@
* TODO: try/catch and if/else are still cumbersome with TypeScriptWriter.
*/
@SmithyInternalApi
final class HttpProtocolTestGenerator implements Runnable {
public final class HttpProtocolTestGenerator implements Runnable {

private static final Logger LOGGER = Logger.getLogger(HttpProtocolTestGenerator.class.getName());
private static final String TEST_CASE_FILE_TEMPLATE = "tests/functional/%s.spec.ts";
Expand All @@ -94,29 +95,32 @@ final class HttpProtocolTestGenerator implements Runnable {
private final Symbol serviceSymbol;
private final Set<String> additionalStubs = new TreeSet<>();
private final ProtocolGenerator protocolGenerator;
private final TestFilter testFilter;
private final GenerationContext context;

/** Vends a TypeScript IFF it's needed. */
private final TypeScriptDelegator delegator;

/** The TypeScript writer that's only allocated once if needed. */
private TypeScriptWriter writer;

HttpProtocolTestGenerator(
TypeScriptSettings settings,
Model model,
ShapeId protocol,
SymbolProvider symbolProvider,
TypeScriptDelegator delegator,
ProtocolGenerator protocolGenerator
public HttpProtocolTestGenerator(
GenerationContext context,
ProtocolGenerator protocolGenerator,
TestFilter testFilter
) {
this.settings = settings;
this.model = model;
this.protocol = protocol;
this.settings = context.getSettings();
this.model = context.getModel();
this.protocol = protocolGenerator.getProtocol();
this.service = settings.getService(model);
this.symbolProvider = symbolProvider;
this.delegator = delegator;
this.symbolProvider = context.getSymbolProvider();
this.protocolGenerator = protocolGenerator;
serviceSymbol = symbolProvider.toSymbol(service);
this.testFilter = testFilter;
this.context = context;
}

public HttpProtocolTestGenerator(
GenerationContext context,
ProtocolGenerator protocolGenerator
) {
this(context, protocolGenerator, (service, operation, testCase, typeScriptSettings) -> false);
}

@Override
Expand Down Expand Up @@ -200,14 +204,14 @@ private void generateServerOperationTests(OperationShape operation, OperationInd
private <T extends HttpMessageTestCase> void onlyIfProtocolMatches(T testCase, Runnable runnable) {
if (testCase.getProtocol().equals(protocol)) {
LOGGER.fine(() -> format("Generating protocol test case for %s.%s", service.getId(), testCase.getId()));
allocateWriterIfNeeded();
initializeWriterIfNeeded();
runnable.run();
}
}

private void allocateWriterIfNeeded() {
private void initializeWriterIfNeeded() {
if (writer == null) {
delegator.useFileWriter(createTestCaseFilename(), writer -> this.writer = writer);
writer = context.getWriter();
writer.addDependency(TypeScriptDependency.AWS_SDK_TYPES);
writer.addDependency(TypeScriptDependency.AWS_SDK_PROTOCOL_HTTP);
// Add the template to each generated test.
Expand All @@ -227,7 +231,7 @@ private void generateClientRequestTest(OperationShape operation, HttpRequestTest

String testName = testCase.getId() + ":Request";
testCase.getDocumentation().ifPresent(writer::writeDocs);
writer.openBlock("it($S, async () => {", "});\n", testName, () -> {
openTestBlock(operation, testCase, testName, () -> {
// Create a client with a custom request handler that intercepts requests.
writer.openBlock("const client = new $T({", "});\n", serviceSymbol, () -> {
writer.write("...clientParams,");
Expand Down Expand Up @@ -277,7 +281,7 @@ private void generateServerRequestTest(OperationShape operation, HttpRequestTest

String testName = testCase.getId() + ":ServerRequest";
testCase.getDocumentation().ifPresent(writer::writeDocs);
writer.openBlock("it($S, async () => {", "});\n", testName, () -> {
openTestBlock(operation, testCase, testName, () -> {
Symbol serviceSymbol = symbolProvider.toSymbol(service);
Symbol handlerSymbol = serviceSymbol.expectProperty("handler", Symbol.class);

Expand Down Expand Up @@ -485,7 +489,7 @@ public void generateServerResponseTest(OperationShape operation, HttpResponseTes
Symbol operationSymbol = symbolProvider.toSymbol(operation);
testCase.getDocumentation().ifPresent(writer::writeDocs);
String testName = testCase.getId() + ":ServerResponse";
writer.openBlock("it($S, async () => {", "});\n", testName, () -> {
openTestBlock(operation, testCase, testName, () -> {
Symbol outputType = operationSymbol.expectProperty("outputType", Symbol.class);
writer.openBlock("class TestService implements Partial<$T> {", "}", serviceSymbol, () -> {
writer.openBlock("$L(input: any, request: HttpRequest): Promise<$T> {", "}",
Expand All @@ -508,7 +512,7 @@ public void generateServerResponseTest(OperationShape operation, HttpResponseTes
private void generateResponseTest(OperationShape operation, HttpResponseTestCase testCase) {
testCase.getDocumentation().ifPresent(writer::writeDocs);
String testName = testCase.getId() + ":Response";
writer.openBlock("it($S, async () => {", "});\n", testName, () -> {
openTestBlock(operation, testCase, testName, () -> {
writeResponseTestSetup(operation, testCase, true);

// Invoke the handler and look for the expected response to then perform assertions.
Expand Down Expand Up @@ -536,8 +540,7 @@ private void generateServerErrorResponseTest(

testCase.getDocumentation().ifPresent(writer::writeDocs);
String testName = testCase.getId() + ":ServerErrorResponse";
writer.openBlock("it($S, async () => {", "});\n", testName, () -> {

openTestBlock(operation, testCase, testName, () -> {
// Generates a Partial implementation of the service type that only includes
// the specific operation under test. Later we'll have to "cast" this with an "as",
// but using the partial in the meantime will give us proper type checking on the
Expand Down Expand Up @@ -624,7 +627,7 @@ private void generateErrorResponseTest(
// we can test for any operation specific values properly.
String testName = testCase.getId() + ":Error:" + operation.getId().getName();
testCase.getDocumentation().ifPresent(writer::writeDocs);
writer.openBlock("it($S, async () => {", "});\n", testName, () -> {
openTestBlock(operation, testCase, testName, () -> {
writeResponseTestSetup(operation, testCase, false);

// Invoke the handler and look for the expected exception to then perform assertions.
Expand Down Expand Up @@ -788,6 +791,20 @@ private void writeParamAssertions(
});
}

private void openTestBlock(
OperationShape operation,
HttpMessageTestCase testCase,
String testName,
Runnable f
) {
// Skipped tests are still generated, just not run.
if (testFilter.skip(service, operation, testCase, settings)) {
writer.openBlock("it.skip($S, async() => {", "});", testName, f);
} else {
writer.openBlock("it($S, async () => {", "});", testName, f);
}
}

/**
* Supports writing out TS specific input types in the generated code
* through visiting the target shape at the same time as the node. If
Expand Down Expand Up @@ -945,6 +962,32 @@ public Void stringNode(StringNode node) {
}
}

/**
* Functional interface for skipping tests.
*/
@FunctionalInterface
public interface TestFilter {
/**
* A function that determines whether or not to skip a test.
*
* <p>A test might be temporarily skipped if it's a known failure that
* will be addressed later, or if the test in question asserts a
* serialized message that can have multiple valid forms.
*
* @param service The service for which tests are being generated.
* @param operation The operation for which tests are being generated.
* @param testCase The test case in question.
* @param settings The settings being used to generate the test service.
* @return True if the test should be skipped, false otherwise.
*/
boolean skip(
ServiceShape service,
OperationShape operation,
HttpMessageTestCase testCase,
TypeScriptSettings settings
);
}

/**
* Supports writing out TS specific output types in the generated code
* through visiting the target shape at the same time as the node. If
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,16 @@ void useFileWriter(String filename, Consumer<TypeScriptWriter> writerConsumer) {
writerConsumer.accept(checkoutWriter(filename));
}

/**
* Gets a previously created writer or creates a new one if needed
* and adds a new line if the writer already exists.
*
* @param filename Name of the file to create.
*/
TypeScriptWriter checkoutFileWriter(String filename) {
return checkoutWriter(filename);
}

private TypeScriptWriter checkoutWriter(String filename) {
String formattedFilename = Paths.get(filename).normalize().toString();
boolean needsNewline = writers.containsKey(formattedFilename);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.util.Collection;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import software.amazon.smithy.codegen.core.CodegenException;
import software.amazon.smithy.codegen.core.Symbol;
Expand Down Expand Up @@ -183,6 +184,13 @@ default void generateSharedComponents(GenerationContext context) {
*/
void generateResponseDeserializers(GenerationContext context);

/**
* Generates protocol tests to assert the protocol works properly.
*
* @param context Generation context.
*/
void generateProtocolTests(GenerationContext context);

/**
* Generates the name of a serializer function for shapes of a service.
*
Expand Down Expand Up @@ -263,6 +271,7 @@ class GenerationContext {
private ServiceShape service;
private SymbolProvider symbolProvider;
private TypeScriptWriter writer;
private Supplier<TypeScriptWriter> writerSupplier;
private List<TypeScriptIntegration> integrations;
private String protocolName;

Expand Down Expand Up @@ -299,11 +308,24 @@ public void setSymbolProvider(SymbolProvider symbolProvider) {
}

public TypeScriptWriter getWriter() {
if (writerSupplier != null && writer == null) {
writer = writerSupplier.get();
}
return writer;
}

public void setWriter(TypeScriptWriter writer) {
this.writer = writer;
if (writer != null) {
this.writerSupplier = null;
}
}

public void setDeferredWriter(Supplier<TypeScriptWriter> writerSupplier) {
this.writerSupplier = writerSupplier;
if (writerSupplier != null) {
this.writer = null;
}
}

public List<TypeScriptIntegration> getIntegrations() {
Expand All @@ -329,6 +351,7 @@ public GenerationContext copy() {
copy.setService(service);
copy.setSymbolProvider(symbolProvider);
copy.setWriter(writer);
copy.setDeferredWriter(writerSupplier);
copy.setIntegrations(integrations);
copy.setProtocolName(protocolName);
return copy;
Expand Down