| // 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 com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows; |
| |
| 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.StarlarkThread.LexicalFrame; |
| import com.google.devtools.build.lib.syntax.StarlarkThread.ReadyToPause; |
| import com.google.devtools.build.lib.syntax.StarlarkThread.Stepping; |
| 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 StarlarkThread}s implementation of {@link StarlarkThread}. */ |
| @RunWith(JUnit4.class) |
| public class StarlarkThreadDebuggingTest { |
| |
| private static StarlarkThread newStarlarkThread() { |
| Mutability mutability = Mutability.create("test"); |
| return StarlarkThread.builder(mutability).useDefaultSemantics().build(); |
| } |
| |
| /** Enter a dummy function scope with the given name, and the current environment's globals. */ |
| private static void enterFunctionScope( |
| StarlarkThread thread, String functionName, Location location) { |
| FuncallExpression ast = new FuncallExpression(Identifier.of("test"), ImmutableList.of()); |
| ast.setLocation(location); |
| thread.enterScope( |
| new BaseFunction(FunctionSignature.ANY) { |
| @Override |
| public String getName() { |
| return functionName; |
| } |
| }, |
| LexicalFrame.create(thread.mutability()), |
| ast, |
| thread.getGlobals()); |
| } |
| |
| @Test |
| public void testListFramesFromGlobalFrame() throws Exception { |
| StarlarkThread thread = newStarlarkThread(); |
| thread.update("a", 1); |
| thread.update("b", 2); |
| thread.update("c", 3); |
| |
| ImmutableList<DebugFrame> frames = thread.listFrames(Location.BUILTIN); |
| |
| assertThat(frames).hasSize(1); |
| assertThat(frames.get(0)) |
| .isEqualTo( |
| DebugFrame.builder() |
| .setFunctionName("<top level>") |
| .setLocation(Location.BUILTIN) |
| .setGlobalBindings(ImmutableMap.of("a", 1, "b", 2, "c", 3)) |
| .build()); |
| } |
| |
| @Test |
| public void testListFramesFromChildFrame() throws Exception { |
| StarlarkThread thread = newStarlarkThread(); |
| thread.update("a", 1); |
| thread.update("b", 2); |
| thread.update("c", 3); |
| Location funcallLocation = |
| Location.fromPathAndStartColumn( |
| PathFragment.create("foo/bar"), 0, 0, new LineAndColumn(12, 0)); |
| enterFunctionScope(thread, "function", funcallLocation); |
| thread.update("a", 4); // shadow parent frame var |
| thread.update("y", 5); |
| thread.update("z", 6); |
| |
| ImmutableList<DebugFrame> frames = thread.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("<top level>") |
| .setLocation(funcallLocation) |
| .setGlobalBindings(ImmutableMap.of("a", 1, "b", 2, "c", 3)) |
| .build()); |
| } |
| |
| @Test |
| public void testStepIntoFunction() { |
| StarlarkThread thread = newStarlarkThread(); |
| |
| ReadyToPause predicate = thread.stepControl(Stepping.INTO); |
| enterFunctionScope(thread, "function", Location.BUILTIN); |
| |
| assertThat(predicate.test(thread)).isTrue(); |
| } |
| |
| @Test |
| public void testStepIntoFallsBackToStepOver() { |
| // test that when stepping into, we'll fall back to stopping at the next statement in the |
| // current frame |
| StarlarkThread thread = newStarlarkThread(); |
| |
| ReadyToPause predicate = thread.stepControl(Stepping.INTO); |
| |
| assertThat(predicate.test(thread)).isTrue(); |
| } |
| |
| @Test |
| public void testStepIntoFallsBackToStepOut() { |
| // test that when stepping into, we'll fall back to stopping when exiting the current frame |
| StarlarkThread thread = newStarlarkThread(); |
| enterFunctionScope(thread, "function", Location.BUILTIN); |
| |
| ReadyToPause predicate = thread.stepControl(Stepping.INTO); |
| thread.exitScope(); |
| |
| assertThat(predicate.test(thread)).isTrue(); |
| } |
| |
| @Test |
| public void testStepOverFunction() { |
| StarlarkThread thread = newStarlarkThread(); |
| |
| ReadyToPause predicate = thread.stepControl(Stepping.OVER); |
| enterFunctionScope(thread, "function", Location.BUILTIN); |
| |
| assertThat(predicate.test(thread)).isFalse(); |
| thread.exitScope(); |
| assertThat(predicate.test(thread)).isTrue(); |
| } |
| |
| @Test |
| public void testStepOverFallsBackToStepOut() { |
| // test that when stepping over, we'll fall back to stopping when exiting the current frame |
| StarlarkThread thread = newStarlarkThread(); |
| enterFunctionScope(thread, "function", Location.BUILTIN); |
| |
| ReadyToPause predicate = thread.stepControl(Stepping.OVER); |
| thread.exitScope(); |
| |
| assertThat(predicate.test(thread)).isTrue(); |
| } |
| |
| @Test |
| public void testStepOutOfInnerFrame() { |
| StarlarkThread thread = newStarlarkThread(); |
| enterFunctionScope(thread, "function", Location.BUILTIN); |
| |
| ReadyToPause predicate = thread.stepControl(Stepping.OUT); |
| |
| assertThat(predicate.test(thread)).isFalse(); |
| thread.exitScope(); |
| assertThat(predicate.test(thread)).isTrue(); |
| } |
| |
| @Test |
| public void testStepOutOfOutermostFrame() { |
| StarlarkThread thread = newStarlarkThread(); |
| |
| assertThat(thread.stepControl(Stepping.OUT)).isNull(); |
| } |
| |
| @Test |
| public void testStepControlWithNoSteppingReturnsNull() { |
| StarlarkThread thread = newStarlarkThread(); |
| |
| assertThat(thread.stepControl(Stepping.NONE)).isNull(); |
| } |
| |
| @Test |
| public void testEvaluateVariableInScope() throws Exception { |
| StarlarkThread thread = newStarlarkThread(); |
| thread.update("a", 1); |
| |
| Object a = thread.debugEval(Expression.parse(ParserInput.fromLines("a"))); |
| assertThat(a).isEqualTo(1); |
| } |
| |
| @Test |
| public void testEvaluateVariableNotInScopeFails() throws Exception { |
| StarlarkThread thread = newStarlarkThread(); |
| thread.update("a", 1); |
| |
| EvalException e = |
| assertThrows( |
| EvalException.class, |
| () -> thread.debugEval(Expression.parse(ParserInput.fromLines("b")))); |
| assertThat(e).hasMessageThat().isEqualTo("name 'b' is not defined"); |
| } |
| |
| @Test |
| public void testEvaluateExpressionOnVariableInScope() throws Exception { |
| StarlarkThread thread = newStarlarkThread(); |
| thread.update("a", "string"); |
| |
| assertThat(thread.debugEval(Expression.parse(ParserInput.fromLines("a.startswith('str')")))) |
| .isEqualTo(true); |
| EvalUtils.exec( |
| EvalUtils.parseAndValidateSkylark(ParserInput.fromLines("a = 1"), thread), thread); |
| assertThat(thread.debugEval(Expression.parse(ParserInput.fromLines("a")))).isEqualTo(1); |
| } |
| } |