1# Copyright 2012-2019, Damian Johnson and The Tor Project
2# See LICENSE for licensing information
3
4"""
5Testing requirements. This provides annotations to skip tests that shouldn't be
6run.
7
8::
9
10  Test Requirements
11  |- only_run_once - skip test if it has been ran before
12  |- needs - skips the test unless a requirement is met
13  |
14  |- cryptography - skips test unless the cryptography module is present
15  |- ed25519_support - skips test unless cryptography has ed25519 support
16  |- command - requires a command to be on the path
17  |- proc - requires the platform to have recognized /proc contents
18  |
19  |- controller - skips test unless tor provides a controller endpoint
20  |- version - skips test unless we meet a tor version requirement
21  |- ptrace - requires 'DisableDebuggerAttachment' to be set
22  +- online - skips unless targets allow for online tests
23"""
24
25import stem.util.system
26import stem.version
27import test
28import test.runner
29
30RAN_TESTS = []
31
32
33def only_run_once(func):
34  """
35  Skips the test if it has ran before. If it hasn't then flags it as being ran.
36  This is useful to prevent lengthy tests that are independent of integ targets
37  from being run repeatedly with ``RUN_ALL``.
38  """
39
40  def wrapped(self, *args, **kwargs):
41    if self.id() not in RAN_TESTS:
42      RAN_TESTS.append(self.id())
43      return func(self, *args, **kwargs)
44    else:
45      self.skipTest('(already ran)')
46
47  return wrapped
48
49
50def needs(condition, message):
51  """
52  Skips the test unless the conditional evaluates to 'true'.
53  """
54
55  def decorator(func):
56    def wrapped(self, *args, **kwargs):
57      if condition():
58        return func(self, *args, **kwargs)
59      else:
60        self.skipTest('(%s)' % message)
61
62    return wrapped
63
64  return decorator
65
66
67def _can_access_controller():
68  return test.runner.get_runner().is_accessible()
69
70
71def _can_ptrace():
72  # If we're running a tor version where ptrace is disabled and we didn't
73  # set 'DisableDebuggerAttachment=1' then we can infer that it's disabled.
74
75  has_option = test.tor_version() >= stem.version.Requirement.TORRC_DISABLE_DEBUGGER_ATTACHMENT
76  return not has_option or test.runner.Torrc.PTRACE in test.runner.get_runner().get_options()
77
78
79def _is_online():
80  return test.Target.ONLINE in test.runner.get_runner().attribute_targets
81
82
83def command(cmd):
84  """
85  Skips the test unless a command is available on the path.
86  """
87
88  return needs(lambda: stem.util.system.is_available(cmd), '%s unavailable' % cmd)
89
90
91def version(req_version):
92  """
93  Skips the test unless we meet the required version.
94
95  :param stem.version.Version req_version: required tor version for the test
96  """
97
98  return needs(lambda: test.tor_version() >= req_version, 'requires %s' % req_version)
99
100
101cryptography = needs(stem.prereq.is_crypto_available, 'requires cryptography')
102ed25519_support = needs(lambda: stem.prereq.is_crypto_available(ed25519 = True), 'requires ed25519 support')
103proc = needs(stem.util.proc.is_available, 'proc unavailable')
104controller = needs(_can_access_controller, 'no connection')
105ptrace = needs(_can_ptrace, 'DisableDebuggerAttachment is set')
106online = needs(_is_online, 'requires online target')
107