stepwise.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. # -*- coding: utf-8 -*-
  2. import pytest
  3. def pytest_addoption(parser):
  4. group = parser.getgroup("general")
  5. group.addoption(
  6. "--sw",
  7. "--stepwise",
  8. action="store_true",
  9. dest="stepwise",
  10. help="exit on test failure and continue from last failing test next time",
  11. )
  12. group.addoption(
  13. "--stepwise-skip",
  14. action="store_true",
  15. dest="stepwise_skip",
  16. help="ignore the first failing test but stop on the next failing test",
  17. )
  18. @pytest.hookimpl
  19. def pytest_configure(config):
  20. config.pluginmanager.register(StepwisePlugin(config), "stepwiseplugin")
  21. class StepwisePlugin:
  22. def __init__(self, config):
  23. self.config = config
  24. self.active = config.getvalue("stepwise")
  25. self.session = None
  26. self.report_status = ""
  27. if self.active:
  28. self.lastfailed = config.cache.get("cache/stepwise", None)
  29. self.skip = config.getvalue("stepwise_skip")
  30. def pytest_sessionstart(self, session):
  31. self.session = session
  32. def pytest_collection_modifyitems(self, session, config, items):
  33. if not self.active:
  34. return
  35. if not self.lastfailed:
  36. self.report_status = "no previously failed tests, not skipping."
  37. return
  38. already_passed = []
  39. found = False
  40. # Make a list of all tests that have been run before the last failing one.
  41. for item in items:
  42. if item.nodeid == self.lastfailed:
  43. found = True
  44. break
  45. else:
  46. already_passed.append(item)
  47. # If the previously failed test was not found among the test items,
  48. # do not skip any tests.
  49. if not found:
  50. self.report_status = "previously failed test not found, not skipping."
  51. already_passed = []
  52. else:
  53. self.report_status = "skipping {} already passed items.".format(
  54. len(already_passed)
  55. )
  56. for item in already_passed:
  57. items.remove(item)
  58. config.hook.pytest_deselected(items=already_passed)
  59. def pytest_runtest_logreport(self, report):
  60. if not self.active:
  61. return
  62. if report.failed:
  63. if self.skip:
  64. # Remove test from the failed ones (if it exists) and unset the skip option
  65. # to make sure the following tests will not be skipped.
  66. if report.nodeid == self.lastfailed:
  67. self.lastfailed = None
  68. self.skip = False
  69. else:
  70. # Mark test as the last failing and interrupt the test session.
  71. self.lastfailed = report.nodeid
  72. self.session.shouldstop = (
  73. "Test failed, continuing from this test next run."
  74. )
  75. else:
  76. # If the test was actually run and did pass.
  77. if report.when == "call":
  78. # Remove test from the failed ones, if exists.
  79. if report.nodeid == self.lastfailed:
  80. self.lastfailed = None
  81. def pytest_report_collectionfinish(self):
  82. if self.active and self.config.getoption("verbose") >= 0 and self.report_status:
  83. return "stepwise: %s" % self.report_status
  84. def pytest_sessionfinish(self, session):
  85. if self.active:
  86. self.config.cache.set("cache/stepwise", self.lastfailed)
  87. else:
  88. # Clear the list of failing tests if the plugin is not active.
  89. self.config.cache.set("cache/stepwise", [])