text: from typing import Callable, Dict, List, Set, Tuple
  
  import py
  import pytest
  
  from dgtest.core.parse import (
      parse_definition_nodes_from_codebase,
      parse_definition_nodes_from_file,
      parse_import_nodes_from_codebase,
      parse_import_nodes_from_file,
      parse_pytest_fixtures_from_codebase,
      parse_pytest_fixtures_from_file,
      parse_pytest_tests_from_codebase,
      parse_pytest_tests_from_file,
      update_dict,
  )
  
  
  @pytest.fixture(scope="function")
  def create_mock_codebase(tmpdir: py.path.local) -> Callable:
      """Utility to easily create temporary filesystems"""
  
      def _create_mock_codebase(
          directory: str, *files: Tuple[str, str]
      ) -> Tuple[str, List[str]]:
          # Create source and tests directories
          my_dir = tmpdir.mkdir(directory)
          my_dir.mkdir("tests")
  
          my_files = []
          for name, contents in files:
              temp_file = my_dir.join(name)
              temp_file.write(contents)
              my_files.append(temp_file.strpath)
          return my_dir.strpath, my_files
  
      return _create_mock_codebase
  
  
  @pytest.fixture(scope="function")
  def definition_map() -> Dict[str, Set[str]]:
      """Used to represent the result of `parse_definition_nodes_from_codebase`"""
      return {
          "reverse_string": {"my_package/strings/strings.py"},
          "concatenate_strings": {"my_package/strings/strings.py"},
          "add_two_numbers": {"my_package/math/addition.py"},
          "Square": {"my_package/math/geometry.py"},
          "Triangle": {"my_package/math/geometry.py"},
      }
  
  
  @pytest.fixture(scope="function")
  def fixture_map() -> Dict[str, Set[str]]:
      """Used to represent the result of `parse_pytest_fixtures_from_codebase`"""
      return {"create_shapes": {"my_package/math/geometry.py"}}
  
  
  def test_parse_definition_nodes_from_file_collects_function_definitions(
      create_mock_codebase: Callable,
  ) -> None:
  
      file_contents = """
  def add_two_numbers(x, y):
      return x + y
  
  def reverse_string(string):
      return string.reverse()
      """
  
      _, my_files = create_mock_codebase("my_dir", ("my_file1.py", file_contents))
      file_definition_map = parse_definition_nodes_from_file(my_files[0])
  
      assert len(file_definition_map) == 2
      assert (
          isinstance(file_definition_map["add_two_numbers"], set)
          and len(file_definition_map["add_two_numbers"]) == 1
      )
      assert (
          isinstance(file_definition_map["reverse_string"], set)
          and len(file_definition_map["reverse_string"]) == 1
      )
  
  
  def test_parse_definition_nodes_from_file_collects_class_definitions(
      create_mock_codebase: Callable,
  ) -> None:
      file_contents = """
  class Square(Shape):
      pass
  
  class Triangle(Shape):
      def __init__(self, x, y, z):
          self.x = x
          self.y = y
          self.z = z
      """
      _, my_files = create_mock_codebase("my_dir", ("my_file1.py", file_contents))
      definition_map = parse_definition_nodes_from_file(my_files[0])
  
      assert len(definition_map) == 2
      assert (
          isinstance(definition_map["Square"], set)
          and my_files[0] in definition_map["Square"]
      )
      assert (
          isinstance(definition_map["Triangle"], set)
          and my_files[0] in definition_map["Triangle"]
      )
  
  
  def test_parse_definition_nodes_from_codebase_accounts_for_namespace_collision(
      create_mock_codebase: Callable,
  ) -> None:
  
      file_contents1 = """
  class Circle(Shape):
      pass
      """
      file_contents2 = """
  class Circle(Shape):
      pass
      """
  
      _, my_files = create_mock_codebase(
          "my_dir", ("my_file1.py", file_contents1), ("my_file2.py", file_contents2)
      )
      definition_map = parse_definition_nodes_from_codebase(my_files)
  
      assert len(definition_map) == 1
      assert (
          isinstance(definition_map["Circle"], set) and len(definition_map["Circle"]) == 2
      )
  
  
  def test_parse_import_nodes_from_file_with_no_relative_imports(
      create_mock_codebase: Callable,
  ) -> None:
      file_contents = """
  import copy
  import datetime
  import json
  import logging
  import os
  from typing import Dict, List, Optional, Union
  from uuid import UUID
      """
      _, my_files = create_mock_codebase("my_dir", ("my_file1.py", file_contents))
  
      imports = parse_import_nodes_from_file(my_files[0], "my_package", {})
      assert imports == set()
  
  
  def test_parse_import_nodes_from_file_with_relative_imports(
      create_mock_codebase: Callable,
      definition_map: Dict[str, Set[str]],
  ) -> None:
      file_contents = """
  from my_package.math.addition import add_two_numbers
  from my_package.math.shapes import Square
  from my_package.strings import reverse_string, concatenate_strings
      """
  
      _, my_files = create_mock_codebase("my_dir", ("my_file.py", file_contents))
      imports = parse_import_nodes_from_file(my_files[0], "my_package", definition_map)
  
      assert len(imports) == 3
      assert all(
          path in imports
          for path in (
              "my_package/math/addition.py",
              "my_package/math/geometry.py",
              "my_package/strings/strings.py",
          )
      )
  
  
  def test_parse_import_nodes_from_file_with_ambiguous_imports(
      create_mock_codebase: Callable,
  ) -> None:
      file_contents = """
  from my_package.math import add_two_numbers
      """
      _, my_files = create_mock_codebase("my_dir", ("my_file.py", file_contents))
  
      # Namespace collision means we need to pick the closest import
      definition_map = {
          "add_two_numbers": {"my_package/math/addition.py", "my_package/core/utils.py"},
      }
  
      imports = parse_import_nodes_from_file(my_files[0], "my_package", definition_map)
      assert len(imports) == 1
  
      # The import statement included "math" so we pick the first option
      assert "my_package/math/addition.py" in imports
      assert "my_package/core/utils.py" not in imports
  
  
  def test_parse_import_nodes_from_codebase(
      create_mock_codebase: Callable, definition_map: Dict[str, Set[str]]
  ) -> None:
      file_contents1 = """
  import copy
  import datetime
  import json
  import logging
  
  from my_package.core import Square
  from my_package.util import add_two_numbers
      """
      file_contents2 = """
  import pandas as pd
  import numpy as np
  import os
  
  from my_package.math import Triangle
  from my_package.strings.strings import reverse_string, concatenate_strings
      """
  
      _, my_files = create_mock_codebase(
          "my_dir", ("my_file1.py", file_contents1), ("my_file2.py", file_contents2)
      )
      imports = parse_import_nodes_from_codebase(my_files, "my_package", definition_map)
      print(imports)
  
      assert len(imports) == 3
      keys = [
          "my_package/math/addition.py",
          "my_package/strings/strings.py",
          "my_package/math/geometry.py",
      ]
      assert all(file in imports for file in keys)
      assert len(imports["my_package/math/addition.py"]) == 1
      assert len(imports["my_package/strings/strings.py"]) == 1
      assert len(imports["my_package/math/geometry.py"]) == 2  # Imported in both files
  
  
  def test_parse_pytest_fixtures_from_file_with_simple_fixtures(
      create_mock_codebase: Callable,
  ) -> None:
      file_contents = """
  @pytest.fixture()
  def my_empty_fixture():
      pass
  
  @pytest.fixture(scope="session")
  def my_other_empty_fixture():
      pass
      """
      _, my_files = create_mock_codebase("my_dir", ("conftest.py", file_contents))
  
      imports = parse_pytest_fixtures_from_file(my_files[0], {})
      assert imports == {}
  
  
  def test_parse_pytest_fixtures_from_file_with_fixtures_referencing_symbols(
      create_mock_codebase: Callable,
      definition_map: Dict[str, Set[str]],
  ) -> None:
      file_contents = """
  @pytest.fixture()
  def create_shapes():
      # my_package/math/geometry.py
      square = Square()
  
      # my_package/math/addition.py
      square.x = add_two_numbers(1, 2)
      square.y = add_two_numbers(3, 4)
  
      return my_class
      """
  
      _, my_files = create_mock_codebase("my_dir", ("conftest.py", file_contents))
      fixtures = parse_pytest_fixtures_from_file(my_files[0], definition_map)
  
      assert len(fixtures) == 1
      assert fixtures["create_shapes"] == {
          "my_package/math/geometry.py",
          "my_package/math/addition.py",
      }
  
  
  # FIXME(cdkini): This functionality needs to be enabled!
  
  # def test_parse_pytest_fixtures_from_file_with_fixtures_referencing_each_other(
  #     create_mock_codebase: Callable,
  #     definition_map: Dict[str, Set[str]],
  # ) -> None:
  #     file_contents = """
  # @pytest.fixture()
  # def my_fixture1():
  #     my_class = Square()
  #     add_two_numbers(my_class)
  #     return my_class
  
  
  # @pytest.fixture()
  # def my_fixture2(my_fixture1): # This should share 'my_fixture1's dependencies
  #     return my_fixture1
  #     """
  
  #     _, my_files = create_mock_codebase("my_dir", ("conftest.py", file_contents))
  #     imports = parse_pytest_fixtures_from_file(my_files[0], definition_map)
  
  #     assert len(imports) == 2
  #     assert imports["my_fixture1"] == {
  #         "my_package/math/geometry.py",
  #         "my_package/math/addition.py",
  #     }
  #     assert imports["my_fixture2"] == {
  #         "my_package/math/geometry.py",
  #         "my_package/math/addition.py",
  #     }
  
  
  def test_parse_pytest_fixtures_from_codebase(
      create_mock_codebase: Callable, definition_map: Dict[str, Set[str]]
  ) -> None:
      file_contents1 = """
  @pytest.fixture
  def create_circle():
      return Circle()
  
  @pytest.fixture(scope="function")
  def create_triangle():
      return Triangle()
      """
      file_contents2 = """
  @pytest.fixture
  def set_up_strings():
      return concatenate_strings("hello", "world")
      """
  
      _, my_files = create_mock_codebase(
          "my_dir", ("conftest.py", file_contents1), ("tests/conftest.py", file_contents2)
      )
      fixtures = parse_pytest_fixtures_from_codebase(my_files, definition_map)
  
      assert len(fixtures) == 2
      print(fixtures.keys())
      assert all(key in fixtures for key in ("create_triangle", "set_up_strings"))
      assert fixtures["create_triangle"] == {"my_package/math/geometry.py"}
      assert fixtures["set_up_strings"] == {"my_package/strings/strings.py"}
  
  
  def test_parse_pytest_tests_from_file_with_imports(
      create_mock_codebase: Callable, definition_map: Dict[str, Set[str]]
  ) -> None:
      test_file_name = "test_shapes.py"
      file_contents = """
  from my_package.math.shapes import Square, Triangle
  
  def test_Square():
      assert Square()
  
  def test_Triangle():
      assert Triangle()
      """
  
      _, my_files = create_mock_codebase("my_dir", (test_file_name, file_contents))
      file_graph = parse_pytest_tests_from_file(
          my_files[0], "my_package", definition_map, fixture_map={}
      )
  
      assert len(file_graph) == 1
      assert len(file_graph["my_package/math/geometry.py"]) == 1
      assert list(file_graph["my_package/math/geometry.py"])[0].endswith("test_shapes.py")
  
  
  def test_parse_pytest_tests_from_file_with_fixtures(
      create_mock_codebase: Callable,
      definition_map: Dict[str, Set[str]],
      fixture_map: Dict[str, Set[str]],
  ) -> None:
      test_file_name = "test_my_class.py"
      file_contents = """
  def test_my_classes(create_shapes):
      pass
  """
  
      _, my_files = create_mock_codebase("my_dir", (test_file_name, file_contents))
  
      file_graph = parse_pytest_tests_from_file(
          my_files[0], "my_package", definition_map, fixture_map
      )
  
      assert len(file_graph) == 1
      assert len(file_graph["my_package/math/geometry.py"]) == 1
      assert list(file_graph["my_package/math/geometry.py"])[0].endswith(
          "test_my_class.py"
      )
  
  
  def test_parse_pytest_tests_from_file_with_imports_and_fixtures(
      create_mock_codebase: Callable,
      definition_map: Dict[str, Set[str]],
      fixture_map: Dict[str, Set[str]],
  ) -> None:
      test_file_name = "test_my_class.py"
      file_contents = """
  from my_package.core import Square
  from my_package.util import add_two_numbers
  
  
  def test_shapes(create_shapes):
      pass
  """
  
      _, my_files = create_mock_codebase("my_dir", (test_file_name, file_contents))
      file_graph = parse_pytest_tests_from_file(
          my_files[0], "my_package", definition_map, fixture_map
      )
  
      assert len(file_graph) == 2
      for file in ("my_package/math/geometry.py", "my_package/math/addition.py"):
          assert len(file_graph[file]) == 1
          assert list(file_graph[file])[0].endswith("test_my_class.py")
  
  
  def test_parse_pytest_tests_from_codebase(
      create_mock_codebase: Callable,
      definition_map: Dict[str, Set[str]],
      fixture_map: Dict[str, Set[str]],
  ) -> None:
      file_contents1 = """
  from my_package.math.shapes import Square
  from my_package.math import add_two_numbers
  
  def test_shapes(create_shapes):
      pass
      """
      file_contents2 = """
  from my_package.math.addition import add_two_numbers
  
  @pytest.fixture()
  def add_nums():
      return add_two_numbers()
  
  def test_math(fake_fixture, add_nums):
      pass
      """
  
      _, my_files = create_mock_codebase(
          "my_dir", ("test_file1.py", file_contents1), ("test_file2.py", file_contents2)
      )
  
      tests_dependency_graph = parse_pytest_tests_from_codebase(
          my_files, "my_package", definition_map, fixture_map
      )
  
      assert len(tests_dependency_graph) == 2
      assert len(tests_dependency_graph["my_package/math/geometry.py"]) == 1
      assert len(tests_dependency_graph["my_package/math/addition.py"]) == 2
  
  
  def test_update_dict_updates_existing_keys() -> None:
      A = {"my_file1": {"a", "b", "c"}}
      B = {"my_file1": {"c", "d", "e"}}
  
      update_dict(A, B)
  
      assert len(A) == 1
      assert all(num in A["my_file1"] for num in ("a", "b", "c", "d", "e"))
  
  
  def test_update_dict_adds_new_keys() -> None:
      A = {"my_file1": {"a", "b", "c"}}
      B = {"bar": {"c", "d", "e"}}
  
      update_dict(A, B)
  
      assert len(A) == 2
      assert all(num in A["my_file1"] for num in ("a", "b", "c"))
      assert all(num in A["bar"] for num in ("c", "d", "e"))
  