/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.template.soy.shared;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.template.soy.data.SoyValue;
import com.google.template.soy.data.SoyValueConverter;
import com.google.template.soy.data.restricted.StringData;
import com.google.template.soy.jssrc.restricted.JsExpr;
import com.google.template.soy.jssrc.restricted.SoyJsSrcPrintDirective;
import com.google.template.soy.shared.restricted.SoyJavaPrintDirective;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import org.junit.Rule;
import org.junit.rules.TestName;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.NativeObject;
import org.mozilla.javascript.ScriptableObject;
/**
* Utilities for testing Soy print directives.
*
* <p>TODO(lukes): this should be changed to be a {@code @Rule}
*
*/
@ParametersAreNonnullByDefault
public abstract class AbstractSoyPrintDirectiveTestCase {
@Rule public final TestName testName = new TestName();
/**
* @param expectedOutput The expected result of applying directive to value with args.
* @param value The test input.
* @param directive The directive whose {@link SoyJavaPrintDirective#applyForJava} is under test.
* @param args Arguments to the Soy directive.
*/
protected void assertTofuOutput(
String expectedOutput,
@Nullable Object value,
SoyJavaPrintDirective directive,
Object... args) {
assertTofuOutput(StringData.forValue(expectedOutput), value, directive, args);
}
/**
* @param expectedOutput The expected result of applying directive to value with args.
* @param value The test input.
* @param directive The directive whose {@link SoyJavaPrintDirective#applyForJava} is under test.
* @param args Arguments to the Soy directive.
*/
protected void assertTofuOutput(
SoyValue expectedOutput, Object value, SoyJavaPrintDirective directive, Object... args) {
ImmutableList.Builder<SoyValue> argsData = ImmutableList.builder();
for (Object arg : args) {
argsData.add(SoyValueConverter.UNCUSTOMIZED_INSTANCE.convert(arg).resolve());
}
assertThat(
directive
.applyForJava(
SoyValueConverter.UNCUSTOMIZED_INSTANCE.convert(value).resolve(),
argsData.build())
.toString())
.isEqualTo(expectedOutput.toString());
}
/** Aggregates multiple JS tests so that they can be run in a single JS interpreter. */
public final class JsSrcPrintDirectiveTestBuilder {
private final ImmutableList.Builder<TestData> testDataListBuilder = ImmutableList.builder();
public JsSrcPrintDirectiveTestBuilder() {
// Nothing to do.
}
/**
* @param expectedOutput The string value that should be produced by directive given the
* accompanying value and arguments.
* @param valueJs Source code for a JavaScript expression that produces a value for the
* directive.
* @param directive The directive under test.
* @param directiveArgsJs Source code for JavaScript expressions that produce the directive's
* arguments.
*/
public JsSrcPrintDirectiveTestBuilder addTest(
String expectedOutput,
String valueJs,
SoyJsSrcPrintDirective directive,
String... directiveArgsJs) {
testDataListBuilder.add(
new TestData(expectedOutput, valueJs, directive, Arrays.asList(directiveArgsJs)));
return this;
}
public void runTests() {
StringBuilder generatedJsExprsAsJsArray = new StringBuilder();
ImmutableList.Builder<String> expectedOutputsListBuilder = ImmutableList.builder();
generatedJsExprsAsJsArray.append("[");
boolean generatedJsExprsAsJsArrayHasElements = false;
for (TestData testData : testDataListBuilder.build()) {
expectedOutputsListBuilder.add(testData.expectedOutput);
ImmutableList.Builder<JsExpr> args = ImmutableList.builder();
for (String argJs : testData.directiveArgsJs) {
args.add(new JsExpr("(" + argJs + ")", Integer.MAX_VALUE));
}
if (generatedJsExprsAsJsArrayHasElements) {
generatedJsExprsAsJsArray.append(",\n");
}
generatedJsExprsAsJsArray
.append("String(")
.append(
testData
.directive
.applyForJsSrc(
new JsExpr("(" + testData.valueJs + ")", Integer.MAX_VALUE), args.build())
.getText())
.append(")");
generatedJsExprsAsJsArrayHasElements = true;
}
generatedJsExprsAsJsArray.append("]");
Context context = new ContextFactory().enterContext();
context.setOptimizationLevel(-1); // Only running once.
ScriptableObject globalScope = context.initStandardObjects();
// Define a fake navigator object for Closure's userAgent library.
NativeObject navigator = new NativeObject();
ScriptableObject.putConstProperty(navigator, "userAgent", "testZilla");
globalScope.defineProperty("navigator", navigator, ScriptableObject.DONTENUM);
// Disable asserts, so that zSoyz will be returned instead of throwing an exception.
NativeObject defines = new NativeObject();
ScriptableObject.putConstProperty(defines, "goog.asserts.ENABLE_ASSERTS", false);
globalScope.defineProperty("CLOSURE_UNCOMPILED_DEFINES", defines, ScriptableObject.CONST);
try {
String soyutilsPath = getSoyUtilsPath();
Reader soyutils = new InputStreamReader(new FileInputStream(soyutilsPath), UTF_8);
try {
String basename = soyutilsPath.substring(soyutilsPath.lastIndexOf('/') + 1);
context.evaluateReader(globalScope, soyutils, basename, 1, null);
} finally {
soyutils.close();
}
} catch (IOException ex) {
throw new AssertionError(ex);
}
NativeArray outputAsJsList =
(NativeArray)
context.evaluateString(
globalScope,
generatedJsExprsAsJsArray.toString(),
getClass() + ":" + testName.getMethodName(), // File name for JS traces.
1,
null);
long n = outputAsJsList.getLength();
ImmutableList.Builder<String> actualOutputListBuilder = ImmutableList.builder();
for (int i = 0; i < n; ++i) {
actualOutputListBuilder.add((String) outputAsJsList.get(i, globalScope));
}
assertThat(Joiner.on('\n').join(actualOutputListBuilder.build()))
.isEqualTo(Joiner.on('\n').join(expectedOutputsListBuilder.build()));
}
}
private static String getSoyUtilsPath() {
return "testdata/javascript/soy_usegoog_lib.js";
}
/** Data for a single print directive test to run in a JS interpreter. */
static final class TestData {
/** The expected output string. */
final String expectedOutput;
/** The print directive value as a JavaScript expression. */
final String valueJs;
/** The directive under test. */
final SoyJsSrcPrintDirective directive;
/** The print directive arguments as a javaScript expression. */
final ImmutableList<String> directiveArgsJs;
TestData(
String expectedOutput,
String valueJs,
SoyJsSrcPrintDirective directive,
List<String> directiveArgsJs) {
this.expectedOutput = expectedOutput;
this.valueJs = valueJs;
this.directive = directive;
this.directiveArgsJs = ImmutableList.copyOf(directiveArgsJs);
}
}
}