/*
* Copyright 2015 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.pysrc.internal;
import static com.google.common.truth.Truth.assertAbout;
import static com.google.common.truth.Truth.assertThat;
import static com.google.template.soy.shared.SharedTestUtils.untypedTemplateBodyForExpression;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.truth.FailureStrategy;
import com.google.common.truth.Subject;
import com.google.common.truth.SubjectFactory;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.template.soy.SoyFileSetParserBuilder;
import com.google.template.soy.SoyModule;
import com.google.template.soy.error.ExplodingErrorReporter;
import com.google.template.soy.exprtree.ExprNode;
import com.google.template.soy.exprtree.Operator;
import com.google.template.soy.internal.i18n.BidiGlobalDir;
import com.google.template.soy.pysrc.internal.GenPyExprsVisitor.GenPyExprsVisitorFactory;
import com.google.template.soy.pysrc.restricted.PyExpr;
import com.google.template.soy.pysrc.restricted.PyExprUtils;
import com.google.template.soy.shared.SharedTestUtils;
import com.google.template.soy.shared.SoyGeneralOptions;
import com.google.template.soy.soytree.PrintNode;
import com.google.template.soy.soytree.SoyFileSetNode;
import com.google.template.soy.soytree.SoyNode;
import java.util.List;
import java.util.Map;
/**
* Truth assertion which compiles the provided soy code and asserts that the generated PyExprs match
* the expected expressions. This subject is only valid for soy code which can be represented as one
* or more Python expressions.
*
*/
public final class SoyExprForPySubject extends Subject<SoyExprForPySubject, String> {
private final SoyGeneralOptions opts = new SoyGeneralOptions();
private final LocalVariableStack localVarExprs;
private final Injector injector;
private SoyExprForPySubject(FailureStrategy failureStrategy, String expr) {
super(failureStrategy, expr);
localVarExprs = new LocalVariableStack();
injector = Guice.createInjector(new SoyModule());
}
/**
* Adds a frame of local variables to the top of the {@link LocalVariableStack}.
*
* @param localVarFrame one frame of local variables
* @return the current subject for chaining
*/
public SoyExprForPySubject with(Map<String, PyExpr> localVarFrame) {
localVarExprs.pushFrame();
for (Map.Entry<String, PyExpr> entry : localVarFrame.entrySet()) {
localVarExprs.addVariable(entry.getKey(), entry.getValue());
}
return this;
}
/**
* Sets a map of key to {@link com.google.template.soy.data.restricted.PrimitiveData} values as
* the current globally available data. Any compilation step will use these globals to replace
* unrecognized variables.
*
* @param globals a map of keys to PrimitiveData values
* @return the current subject for chaining
*/
public SoyExprForPySubject withGlobals(ImmutableMap<String, ?> globals) {
opts.setCompileTimeGlobals(globals);
return this;
}
/**
* Asserts the subject compiles to the correct PyExpr.
*
* @param expectedPyExpr the expected result of compilation
*/
public void compilesTo(PyExpr expectedPyExpr) {
compilesTo(ImmutableList.of(expectedPyExpr));
}
/**
* Asserts the subject compiles to the correct list of PyExprs.
*
* <p>The given Soy expr is wrapped in a full body of a template. The actual result is replaced
* with ids for ### so that tests don't break when ids change.
*
* @param expectedPyExprs the expected result of compilation
*/
public void compilesTo(List<PyExpr> expectedPyExprs) {
SoyFileSetNode soyTree =
SoyFileSetParserBuilder.forTemplateContents(getSubject()).parse().fileSet();
SoyNode node = SharedTestUtils.getNode(soyTree, 0);
SharedTestUtils.simulateNewApiCall(injector, null, BidiGlobalDir.LTR);
GenPyExprsVisitor genPyExprsVisitor =
injector
.getInstance(GenPyExprsVisitorFactory.class)
.create(localVarExprs, ExplodingErrorReporter.get());
List<PyExpr> actualPyExprs = genPyExprsVisitor.exec(node);
assertThat(actualPyExprs).hasSize(expectedPyExprs.size());
for (int i = 0; i < expectedPyExprs.size(); i++) {
PyExpr expectedPyExpr = expectedPyExprs.get(i);
PyExpr actualPyExpr = actualPyExprs.get(i);
assertThat(actualPyExpr.getText().replaceAll("\\([0-9]+", "(###"))
.isEqualTo(expectedPyExpr.getText());
assertThat(actualPyExpr.getPrecedence()).isEqualTo(expectedPyExpr.getPrecedence());
}
}
/**
* Asserts the subject translates to the expected PyExpr.
*
* @param expectedPyExpr the expected result of translation
*/
public void translatesTo(PyExpr expectedPyExpr) {
translatesTo(expectedPyExpr, null);
}
public void translatesTo(String expr, Operator precedence) {
translatesTo(new PyExpr(expr, PyExprUtils.pyPrecedenceForOperator(precedence)));
}
public void translatesTo(String expr, int precedence) {
translatesTo(new PyExpr(expr, precedence));
}
/**
* Asserts the subject translates to the expected PyExpr including verification of the exact
* PyExpr class (e.g. {@code PyStringExpr.class}).
*
* @param expectedPyExpr the expected result of translation
* @param expectedClass the expected class of the resulting PyExpr
*/
public void translatesTo(PyExpr expectedPyExpr, Class<? extends PyExpr> expectedClass) {
SoyFileSetNode soyTree =
SoyFileSetParserBuilder.forTemplateContents(untypedTemplateBodyForExpression(getSubject()))
.options(opts)
.parse()
.fileSet();
PrintNode node = (PrintNode) SharedTestUtils.getNode(soyTree, 0);
ExprNode exprNode = node.getExpr();
PyExpr actualPyExpr =
new TranslateToPyExprVisitor(localVarExprs, ExplodingErrorReporter.get()).exec(exprNode);
assertThat(actualPyExpr.getText()).isEqualTo(expectedPyExpr.getText());
assertThat(actualPyExpr.getPrecedence()).isEqualTo(expectedPyExpr.getPrecedence());
if (expectedClass != null) {
assertThat(actualPyExpr.getClass()).isEqualTo(expectedClass);
}
}
//-----------------------------------------------------------------------------------------------
// Public static functions for starting a SoyExprForPySubject test.
private static final SubjectFactory<SoyExprForPySubject, String> SOYEXPR =
new SubjectFactory<SoyExprForPySubject, String>() {
@Override
public SoyExprForPySubject getSubject(FailureStrategy failureStrategy, String expr) {
return new SoyExprForPySubject(failureStrategy, expr);
}
};
public static SoyExprForPySubject assertThatSoyExpr(String expr) {
return assertAbout(SOYEXPR).that(expr);
}
}