// Copyright 2018 The Bazel Authors. All Rights Reserved. // // 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.devtools.build.lib.syntax; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.events.Location.LineAndColumn; import com.google.devtools.build.lib.syntax.Debuggable.ReadyToPause; import com.google.devtools.build.lib.syntax.Debuggable.Stepping; import com.google.devtools.build.lib.syntax.Environment.LexicalFrame; import com.google.devtools.build.lib.vfs.PathFragment; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Unit tests of {@link Environment}s implementation of {@link Debuggable}. */ @RunWith(JUnit4.class) public class EnvironmentDebuggingTest { private static Environment newEnvironment() { Mutability mutability = Mutability.create("test"); return Environment.builder(mutability).useDefaultSemantics().build(); } /** Enter a dummy function scope with the given name, and the current environment's globals. */ private static void enterFunctionScope(Environment env, String functionName, Location location) { FuncallExpression ast = new FuncallExpression(Identifier.of("test"), ImmutableList.of()); ast.setLocation(location); env.enterScope( new BaseFunction(functionName) {}, LexicalFrame.create(env.mutability()), ast, env.getGlobals()); } @Test public void testListFramesFromGlobalFrame() throws Exception { Environment env = newEnvironment(); env.update("a", 1); env.update("b", 2); env.update("c", 3); ImmutableList frames = env.listFrames(Location.BUILTIN); assertThat(frames).hasSize(1); assertThat(frames.get(0)) .isEqualTo( DebugFrame.builder() .setFunctionName("") .setLocation(Location.BUILTIN) .setGlobalBindings(ImmutableMap.of("a", 1, "b", 2, "c", 3)) .build()); } @Test public void testListFramesFromChildFrame() throws Exception { Environment env = newEnvironment(); env.update("a", 1); env.update("b", 2); env.update("c", 3); Location funcallLocation = Location.fromPathAndStartColumn( PathFragment.create("foo/bar"), 0, 0, new LineAndColumn(12, 0)); enterFunctionScope(env, "function", funcallLocation); env.update("a", 4); // shadow parent frame var env.update("y", 5); env.update("z", 6); ImmutableList frames = env.listFrames(Location.BUILTIN); assertThat(frames).hasSize(2); assertThat(frames.get(0)) .isEqualTo( DebugFrame.builder() .setFunctionName("function") .setLocation(Location.BUILTIN) .setLexicalFrameBindings(ImmutableMap.of("a", 4, "y", 5, "z", 6)) .setGlobalBindings(ImmutableMap.of("a", 1, "b", 2, "c", 3)) .build()); assertThat(frames.get(1)) .isEqualTo( DebugFrame.builder() .setFunctionName("") .setLocation(funcallLocation) .setGlobalBindings(ImmutableMap.of("a", 1, "b", 2, "c", 3)) .build()); } @Test public void testStepIntoFunction() { Environment env = newEnvironment(); ReadyToPause predicate = env.stepControl(Stepping.INTO); enterFunctionScope(env, "function", Location.BUILTIN); assertThat(predicate.test(env)).isTrue(); } @Test public void testStepIntoFallsBackToStepOver() { // test that when stepping into, we'll fall back to stopping at the next statement in the // current frame Environment env = newEnvironment(); ReadyToPause predicate = env.stepControl(Stepping.INTO); assertThat(predicate.test(env)).isTrue(); } @Test public void testStepIntoFallsBackToStepOut() { // test that when stepping into, we'll fall back to stopping when exiting the current frame Environment env = newEnvironment(); enterFunctionScope(env, "function", Location.BUILTIN); ReadyToPause predicate = env.stepControl(Stepping.INTO); env.exitScope(); assertThat(predicate.test(env)).isTrue(); } @Test public void testStepOverFunction() { Environment env = newEnvironment(); ReadyToPause predicate = env.stepControl(Stepping.OVER); enterFunctionScope(env, "function", Location.BUILTIN); assertThat(predicate.test(env)).isFalse(); env.exitScope(); assertThat(predicate.test(env)).isTrue(); } @Test public void testStepOverFallsBackToStepOut() { // test that when stepping over, we'll fall back to stopping when exiting the current frame Environment env = newEnvironment(); enterFunctionScope(env, "function", Location.BUILTIN); ReadyToPause predicate = env.stepControl(Stepping.OVER); env.exitScope(); assertThat(predicate.test(env)).isTrue(); } @Test public void testStepOutOfInnerFrame() { Environment env = newEnvironment(); enterFunctionScope(env, "function", Location.BUILTIN); ReadyToPause predicate = env.stepControl(Stepping.OUT); assertThat(predicate.test(env)).isFalse(); env.exitScope(); assertThat(predicate.test(env)).isTrue(); } @Test public void testStepOutOfOutermostFrame() { Environment env = newEnvironment(); assertThat(env.stepControl(Stepping.OUT)).isNull(); } @Test public void testStepControlWithNoSteppingReturnsNull() { Environment env = newEnvironment(); assertThat(env.stepControl(Stepping.NONE)).isNull(); } @Test public void testEvaluateVariableInScope() throws Exception { Environment env = newEnvironment(); env.update("a", 1); Object result = env.evaluate("a"); assertThat(result).isEqualTo(1); } @Test public void testEvaluateVariableNotInScopeFails() throws Exception { Environment env = newEnvironment(); env.update("a", 1); try { env.evaluate("b"); fail(); } catch (EvalException e) { assertThat(e).hasMessageThat().isEqualTo("name 'b' is not defined"); } } @Test public void testEvaluateExpressionOnVariableInScope() throws Exception { Environment env = newEnvironment(); env.update("a", "string"); Object result = env.evaluate("a.startswith(\"str\")"); assertThat(result).isEqualTo(Boolean.TRUE); } }