text: from contextlib import contextmanager
  import sys
  from tempfile import NamedTemporaryFile
  try:
      from unittest import mock
  except ImportError:
      import mock
  
  import pytest
  
  import pipdeptree as p
  
  
  # Tests for DAG classes
  
  def mock_pkgs(simple_graph):
      for node, children in simple_graph.items():
          nk, nv = node
          p = mock.Mock(key=nk, project_name=nk, version=nv)
          as_req = mock.Mock(key=nk, project_name=nk, specs=[('==', nv)])
          p.as_requirement = mock.Mock(return_value=as_req)
          reqs = []
          for child in children:
              ck, cv = child
              r = mock.Mock(key=ck, project_name=ck, specs=cv)
              reqs.append(r)
          p.requires = mock.Mock(return_value=reqs)
          yield p
  
  
  def mock_PackageDAG(simple_graph):
      pkgs = list(mock_pkgs(simple_graph))
      return p.PackageDAG.from_pkgs(pkgs)
  
  
  # util for comparing tree contents with a simple graph
  def dag_to_dict(g):
      return {k.key: [v.key for v in vs] for k, vs in g._obj.items()}
  
  
  def sort_map_values(m):
      return {k: sorted(v) for k, v in m.items()}
  
  
  t = mock_PackageDAG({
      ('a', '3.4.0'): [('b', [('>=', '2.0.0')]),
                       ('c', [('>=', '5.7.1')])],
      ('b', '2.3.1'): [('d', [('>=', '2.30'), ('<', '2.42')])],
      ('c', '5.10.0'): [('d', [('>=', '2.30')]),
                        ('e', [('>=', '0.12.1')])],
      ('d', '2.35'): [('e', [('>=', '0.9.0')])],
      ('e', '0.12.1'): [],
      ('f', '3.1'): [('b', [('>=', '2.1.0')])],
      ('g', '6.8.3rc1'): [('e', [('>=', '0.9.0')]),
                          ('f', [('>=', '3.0.0')])]
  })
  
  
  def test_PackageDAG__get_node_as_parent():
      assert 'b' == t.get_node_as_parent('b').key
      assert 'c' == t.get_node_as_parent('c').key
  
  
  def test_PackageDAG_filter():
      # When both show_only and exclude are not specified, same tree
      # object is returned
      assert t.filter(None, None) is t
  
      # when show_only is specified
      g1 = dag_to_dict(t.filter(set(['a', 'd']), None))
      expected = {'a': ['b', 'c'],
                  'b': ['d'],
                  'c': ['d', 'e'],
                  'd': ['e'],
                  'e': []}
      assert expected == g1
  
      # when exclude is specified
      g2 = dag_to_dict(t.filter(None, ['d']))
      expected = {'a': ['b', 'c'],
                  'b': [],
                  'c': ['e'],
                  'e': [],
                  'f': ['b'],
                  'g': ['e', 'f']}
      assert expected == g2
  
      # when both show_only and exclude are specified
      g3 = dag_to_dict(t.filter(set(['a', 'g']), set(['d', 'e'])))
      expected = {'a': ['b', 'c'],
                  'b': [],
                  'c': [],
                  'f': ['b'],
                  'g': ['f']}
      assert expected == g3
  
      # when conflicting values in show_only and exclude, AssertionError
      # is raised
      with pytest.raises(AssertionError):
          dag_to_dict(t.filter(set(['d']), set(['D', 'e'])))
  
  
  def test_PackageDAG_reverse():
      t1 = t.reverse()
      expected = {'a': [],
                  'b': ['a', 'f'],
                  'c': ['a'],
                  'd': ['b', 'c'],
                  'e': ['c', 'd', 'g'],
                  'f': ['g'],
                  'g': []}
      assert isinstance(t1, p.ReversedPackageDAG)
      assert sort_map_values(expected) == sort_map_values(dag_to_dict(t1))
      assert all([isinstance(k, p.ReqPackage) for k in t1.keys()])
      assert all([isinstance(v, p.DistPackage) for v in p.flatten(t1.values())])
  
      # testing reversal of ReversedPackageDAG instance
      expected = {'a': ['b', 'c'],
                  'b': ['d'],
                  'c': ['d', 'e'],
                  'd': ['e'],
                  'e': [],
                  'f': ['b'],
                  'g': ['e', 'f']}
      t2 = t1.reverse()
      assert isinstance(t2, p.PackageDAG)
      assert sort_map_values(expected) == sort_map_values(dag_to_dict(t2))
      assert all([isinstance(k, p.DistPackage) for k in t2.keys()])
      assert all([isinstance(v, p.ReqPackage) for v in p.flatten(t2.values())])
  
  
  # Tests for Package classes
  #
  # Note: For all render methods, we are only testing for frozen=False
  # as mocks with frozen=True are a lot more complicated
  
  def test_DistPackage__render_as_root():
      foo = mock.Mock(key='foo', project_name='foo', version='20.4.1')
      dp = p.DistPackage(foo)
      is_frozen = False
      assert 'foo==20.4.1' == dp.render_as_root(is_frozen)
  
  
  def test_DistPackage__render_as_branch():
      foo = mock.Mock(key='foo', project_name='foo', version='20.4.1')
      bar = mock.Mock(key='bar', project_name='bar', version='4.1.0')
      bar_req = mock.Mock(key='bar',
                          project_name='bar',
                          version='4.1.0',
                          specs=[('>=', '4.0')])
      rp = p.ReqPackage(bar_req, dist=bar)
      dp = p.DistPackage(foo).as_parent_of(rp)
      is_frozen = False
      assert 'foo==20.4.1 [requires: bar>=4.0]' == dp.render_as_branch(is_frozen)
  
  
  def test_DistPackage__as_parent_of():
      foo = mock.Mock(key='foo', project_name='foo', version='20.4.1')
      dp = p.DistPackage(foo)
      assert dp.req is None
  
      bar = mock.Mock(key='bar', project_name='bar', version='4.1.0')
      bar_req = mock.Mock(key='bar',
                          project_name='bar',
                          version='4.1.0',
                          specs=[('>=', '4.0')])
      rp = p.ReqPackage(bar_req, dist=bar)
      dp1 = dp.as_parent_of(rp)
      assert dp1._obj == dp._obj
      assert dp1.req is rp
  
      dp2 = dp.as_parent_of(None)
      assert dp2 is dp
  
  
  def test_DistPackage__as_dict():
      foo = mock.Mock(key='foo', project_name='foo', version='1.3.2b1')
      dp = p.DistPackage(foo)
      result = dp.as_dict()
      expected = {'key': 'foo',
                  'package_name': 'foo',
                  'installed_version': '1.3.2b1'}
      assert expected == result
  
  
  def test_ReqPackage__render_as_root():
      bar = mock.Mock(key='bar', project_name='bar', version='4.1.0')
      bar_req = mock.Mock(key='bar',
                          project_name='bar',
                          version='4.1.0',
                          specs=[('>=', '4.0')])
      rp = p.ReqPackage(bar_req, dist=bar)
      is_frozen = False
      assert 'bar==4.1.0' == rp.render_as_root(is_frozen)
  
  
  def test_ReqPackage__render_as_branch():
      bar = mock.Mock(key='bar', project_name='bar', version='4.1.0')
      bar_req = mock.Mock(key='bar',
                          project_name='bar',
                          version='4.1.0',
                          specs=[('>=', '4.0')])
      rp = p.ReqPackage(bar_req, dist=bar)
      is_frozen = False
      assert 'bar [required: >=4.0, installed: 4.1.0]' == rp.render_as_branch(is_frozen)
  
  
  def test_ReqPackage__as_dict():
      bar = mock.Mock(key='bar', project_name='bar', version='4.1.0')
      bar_req = mock.Mock(key='bar',
                          project_name='bar',
                          version='4.1.0',
                          specs=[('>=', '4.0')])
      rp = p.ReqPackage(bar_req, dist=bar)
      result = rp.as_dict()
      expected = {'key': 'bar',
                  'package_name': 'bar',
                  'installed_version': '4.1.0',
                  'required_version': '>=4.0'}
      assert expected == result
  
  
  # Tests for render_text
  #
  # @NOTE: These tests use mocked tree and it's not easy to test for
  # frozen=True with mocks. Hence those tests are covered only in
  # end-to-end tests. Check the ./e2e-tests script.
  
  @pytest.mark.parametrize(
      "list_all,reverse,expected_output",
      [
          (
              True,
              False,
              [
                  'a==3.4.0',
                  '  - b [required: >=2.0.0, installed: 2.3.1]',
                  '    - d [required: >=2.30,<2.42, installed: 2.35]',
                  '      - e [required: >=0.9.0, installed: 0.12.1]',
                  '  - c [required: >=5.7.1, installed: 5.10.0]',
                  '    - d [required: >=2.30, installed: 2.35]',
                  '      - e [required: >=0.9.0, installed: 0.12.1]',
                  '    - e [required: >=0.12.1, installed: 0.12.1]',
                  'b==2.3.1',
                  '  - d [required: >=2.30,<2.42, installed: 2.35]',
                  '    - e [required: >=0.9.0, installed: 0.12.1]',
                  'c==5.10.0',
                  '  - d [required: >=2.30, installed: 2.35]',
                  '    - e [required: >=0.9.0, installed: 0.12.1]',
                  '  - e [required: >=0.12.1, installed: 0.12.1]',
                  'd==2.35',
                  '  - e [required: >=0.9.0, installed: 0.12.1]',
                  'e==0.12.1',
                  'f==3.1',
                  '  - b [required: >=2.1.0, installed: 2.3.1]',
                  '    - d [required: >=2.30,<2.42, installed: 2.35]',
                  '      - e [required: >=0.9.0, installed: 0.12.1]',
                  'g==6.8.3rc1',
                  '  - e [required: >=0.9.0, installed: 0.12.1]',
                  '  - f [required: >=3.0.0, installed: 3.1]',
                  '    - b [required: >=2.1.0, installed: 2.3.1]',
                  '      - d [required: >=2.30,<2.42, installed: 2.35]',
                  '        - e [required: >=0.9.0, installed: 0.12.1]'
              ]
          ),
          (
              True,
              True,
              [
                  'a==3.4.0',
                  'b==2.3.1',
                  '  - a==3.4.0 [requires: b>=2.0.0]',
                  '  - f==3.1 [requires: b>=2.1.0]',
                  '    - g==6.8.3rc1 [requires: f>=3.0.0]',
                  'c==5.10.0',
                  '  - a==3.4.0 [requires: c>=5.7.1]',
                  'd==2.35',
                  '  - b==2.3.1 [requires: d>=2.30,<2.42]',
                  '    - a==3.4.0 [requires: b>=2.0.0]',
                  '    - f==3.1 [requires: b>=2.1.0]',
                  '      - g==6.8.3rc1 [requires: f>=3.0.0]',
                  '  - c==5.10.0 [requires: d>=2.30]',
                  '    - a==3.4.0 [requires: c>=5.7.1]',
                  'e==0.12.1',
                  '  - c==5.10.0 [requires: e>=0.12.1]',
                  '    - a==3.4.0 [requires: c>=5.7.1]',
                  '  - d==2.35 [requires: e>=0.9.0]',
                  '    - b==2.3.1 [requires: d>=2.30,<2.42]',
                  '      - a==3.4.0 [requires: b>=2.0.0]',
                  '      - f==3.1 [requires: b>=2.1.0]',
                  '        - g==6.8.3rc1 [requires: f>=3.0.0]',
                  '    - c==5.10.0 [requires: d>=2.30]',
                  '      - a==3.4.0 [requires: c>=5.7.1]',
                  '  - g==6.8.3rc1 [requires: e>=0.9.0]',
                  'f==3.1',
                  '  - g==6.8.3rc1 [requires: f>=3.0.0]',
                  'g==6.8.3rc1'
              ]
          ),
          (
              False,
              False,
              [
                  'a==3.4.0',
                  '  - b [required: >=2.0.0, installed: 2.3.1]',
                  '    - d [required: >=2.30,<2.42, installed: 2.35]',
                  '      - e [required: >=0.9.0, installed: 0.12.1]',
                  '  - c [required: >=5.7.1, installed: 5.10.0]',
                  '    - d [required: >=2.30, installed: 2.35]',
                  '      - e [required: >=0.9.0, installed: 0.12.1]',
                  '    - e [required: >=0.12.1, installed: 0.12.1]',
                  'g==6.8.3rc1',
                  '  - e [required: >=0.9.0, installed: 0.12.1]',
                  '  - f [required: >=3.0.0, installed: 3.1]',
                  '    - b [required: >=2.1.0, installed: 2.3.1]',
                  '      - d [required: >=2.30,<2.42, installed: 2.35]',
                  '        - e [required: >=0.9.0, installed: 0.12.1]',
              ]
          ),
          (
              False,
              True,
              [
                  'e==0.12.1',
                  '  - c==5.10.0 [requires: e>=0.12.1]',
                  '    - a==3.4.0 [requires: c>=5.7.1]',
                  '  - d==2.35 [requires: e>=0.9.0]',
                  '    - b==2.3.1 [requires: d>=2.30,<2.42]',
                  '      - a==3.4.0 [requires: b>=2.0.0]',
                  '      - f==3.1 [requires: b>=2.1.0]',
                  '        - g==6.8.3rc1 [requires: f>=3.0.0]',
                  '    - c==5.10.0 [requires: d>=2.30]',
                  '      - a==3.4.0 [requires: c>=5.7.1]',
                  '  - g==6.8.3rc1 [requires: e>=0.9.0]',
              ]
          )
      ]
  )
  def test_render_text(capsys, list_all, reverse, expected_output):
      tree = t.reverse() if reverse else t
      p.render_text(tree, list_all=list_all, frozen=False)
      captured = capsys.readouterr()
      assert '\n'.join(expected_output).strip() == captured.out.strip()
  
  
  # Tests for graph outputs
  
  def test_render_pdf():
      output = p.dump_graphviz(t, output_format='pdf')
  
      @contextmanager
      def redirect_stdout(new_target):
          old_target, sys.stdout = sys.stdout, new_target
          try:
              yield new_target
          finally:
              sys.stdout = old_target
  
      with NamedTemporaryFile(delete=True) as f:
          with redirect_stdout(f):
              p.print_graphviz(output)
          rf = open(f.name, 'rb')
          assert b'%PDF' == rf.read()[:4]
          # @NOTE: rf is not closed to avoid "bad filedescriptor" error
  
  
  def test_render_svg(capsys):
      output = p.dump_graphviz(t, output_format='svg')
      p.print_graphviz(output)
      out, _ = capsys.readouterr()
      assert out.startswith('<?xml')
      assert '<svg' in out
      assert out.strip().endswith('</svg>')
  
  
  # Test for conflicting deps
  
  @pytest.mark.parametrize(
      "mpkgs,expected_keys,expected_output",
      [
          (
              {
                  ('a', '1.0.1'): [('b', [('>=', '2.3.0')])],
                  ('b', '1.9.1'): []
              },
              {'a': ['b']},
              [
                  'Warning!!! Possibly conflicting dependencies found:',
                  '* a==1.0.1',
                  ' - b [required: >=2.3.0, installed: 1.9.1]'
              ]
          ),
          (
              {
                  ('a', '1.0.1'): [('c', [('>=', '9.4.1')])],
                  ('b', '2.3.0'): [('c', [('>=', '7.0')])],
                  ('c', '8.0.1'): []
              },
              {'a': ['c']},
              [
                  'Warning!!! Possibly conflicting dependencies found:',
                  '* a==1.0.1',
                  ' - c [required: >=9.4.1, installed: 8.0.1]'
              ]
          ),
          (
              {
                  ('a', '1.0.1'): [('c', [('>=', '9.4.1')])],
                  ('b', '2.3.0'): [('c', [('>=', '9.4.0')])]
              },
              {'a': ['c'], 'b': ['c']},
              [
                  'Warning!!! Possibly conflicting dependencies found:',
                  '* a==1.0.1',
                  ' - c [required: >=9.4.1, installed: ?]',
                  '* b==2.3.0',
                  ' - c [required: >=9.4.0, installed: ?]'
              ]
          ),
          (
              {
                  ('a', '1.0.1'): [('c', [('>=', '9.4.1')])],
                  ('b', '2.3.0'): [('c', [('>=', '7.0')])],
                  ('c', '9.4.1'): []
              },
              {},
              []
          )
      ]
  )
  def test_conflicting_deps(capsys, mpkgs, expected_keys, expected_output):
      tree = mock_PackageDAG(mpkgs)
      result = p.conflicting_deps(tree)
      result_keys = {k.key: [v.key for v in vs]
                     for k, vs in result.items()}
      assert expected_keys == result_keys
      p.render_conflicts_text(result)
      captured = capsys.readouterr()
      assert '\n'.join(expected_output).strip() == captured.err.strip()
  
  
  # Tests for cyclic deps
  
  @pytest.mark.parametrize(
      "mpkgs,expected_keys,expected_output",
      [
          (
              {
                  ('a', '1.0.1'): [('b', [('>=', '2.0.0')])],
                  ('b', '2.3.0'): [('a', [('>=', '1.0.1')])],
                  ('c', '4.5.0'): [('d', [('==', '2.0')])],
                  ('d', '2.0'): []
              },
              [('a', 'b', 'a'), ('b', 'a', 'b')],
              [
                  'Warning!! Cyclic dependencies found:',
                  '* b => a => b',
                  '* a => b => a'
              ]
          ),
          ( # if a dependency isn't installed, cannot verify cycles
              {
                  ('a', '1.0.1'): [('b', [('>=', '2.0.0')])],
              },
              [],
              [] # no output expected
          )
      ]
  )
  def test_cyclic_deps(capsys, mpkgs, expected_keys, expected_output):
      tree = mock_PackageDAG(mpkgs)
      result = p.cyclic_deps(tree)
      result_keys = [(a.key, b.key, c.key) for (a, b, c) in result]
      assert sorted(expected_keys) == sorted(result_keys)
      p.render_cycles_text(result)
      captured = capsys.readouterr()
      assert '\n'.join(expected_output).strip() == captured.err.strip()
  
  
  # Tests for the argparse parser
  
  def test_parser_default():
      parser = p.get_parser()
      args = parser.parse_args([])
      assert not args.json
      assert args.output_format is None
  
  
  def test_parser_j():
      parser = p.get_parser()
      args = parser.parse_args(['-j'])
      assert args.json
      assert args.output_format is None
  
  
  def test_parser_json():
      parser = p.get_parser()
      args = parser.parse_args(['--json'])
      assert args.json
      assert args.output_format is None
  
  
  def test_parser_json_tree():
      parser = p.get_parser()
      args = parser.parse_args(['--json-tree'])
      assert args.json_tree
      assert not args.json
      assert args.output_format is None
  
  
  def test_parser_pdf():
      parser = p.get_parser()
      args = parser.parse_args(['--graph-output', 'pdf'])
      assert args.output_format == 'pdf'
      assert not args.json
  
  
  def test_parser_svg():
      parser = p.get_parser()
      args = parser.parse_args(['--graph-output', 'svg'])
      assert args.output_format == 'svg'
      assert not args.json
  