Avoid TestCase mixins that break pytype type inference

- Move abstract TestCase classe to tests/benchmarks/helper.py
- Add more type annotations to mockbenchmark.py

Change-Id: Iaea227afdc1be74d720697bf3c3f95bfb9d3577e
Reviewed-on: https://chromium-review.googlesource.com/c/crossbench/+/3963549
Reviewed-by: Victor Gomes <victorgomes@chromium.org>
diff --git a/tests/benchmarks/__init__.py b/tests/benchmarks/__init__.py
index 736eb17..005a4a1 100644
--- a/tests/benchmarks/__init__.py
+++ b/tests/benchmarks/__init__.py
@@ -1,75 +1,3 @@
 # Copyright 2022 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
-
-import abc
-
-from .. import mockbenchmark
-
-import crossbench as cb
-
-
-class BaseBenchmarkTestCase(
-    mockbenchmark.BaseCrossbenchTestCase, metaclass=abc.ABCMeta):
-
-  @property
-  @abc.abstractmethod
-  def benchmark_cls(self):
-    pass
-
-  @property
-  def story_cls(self):
-    return self.benchmark_cls.DEFAULT_STORY_CLS
-
-  def setUp(self):
-    super().setUp()
-    self.assertTrue(
-        issubclass(self.benchmark_cls, cb.benchmarks.Benchmark),
-        f"Expected Benchmark subclass, but got: BENCHMARK={self.benchmark_cls}")
-
-
-class BenchmarkTestCaseMixin:
-
-  def test_stories_creation(self):
-    for name in self.story_cls.story_names():
-      stories = self.story_cls.from_names([name])
-      self.assertTrue(len(stories) == 1)
-      story = stories[0]
-      self.assertIsInstance(story, self.story_cls)
-      self.assertIsInstance(story.details_json(), dict)
-      self.assertTrue(len(str(story)) > 0)
-
-  def test_instantiate_no_stories(self):
-    with self.assertRaises(AssertionError):
-      self.benchmark_cls(stories=[])
-    with self.assertRaises(AssertionError):
-      self.benchmark_cls(stories="")
-    with self.assertRaises(AssertionError):
-      self.benchmark_cls(stories=["", ""])
-
-  def test_instantiate_single_story(self):
-    any_story_name = self.story_cls.story_names()[0]
-    any_story = self.story_cls.from_names([any_story_name])[0]
-    # Instantiate with single story,
-    with self.assertRaises(TypeError):
-      self.benchmark_cls(any_story)
-    # with single story array
-    self.benchmark_cls([any_story])
-    with self.assertRaises(AssertionError):
-      # Accidentally nested array.
-      self.benchmark_cls([[any_story]])
-
-  def test_instantiate_all_stories(self):
-    stories = self.story_cls.from_names(self.story_cls.story_names())
-    self.benchmark_cls(stories)
-
-  def test_describe(self):
-    self.assertIsInstance(self.benchmark_cls.describe(), dict)
-
-
-class PressBenchmarkTestCaseMixin(BenchmarkTestCaseMixin):
-
-  def test_invalid_story_names(self):
-    with self.assertRaises(Exception):
-      # Only one regexp entry will work
-      self.story_cls.from_names([".*", 'a'], separate=True)
diff --git a/tests/benchmarks/helper.py b/tests/benchmarks/helper.py
new file mode 100644
index 0000000..33df577
--- /dev/null
+++ b/tests/benchmarks/helper.py
@@ -0,0 +1,88 @@
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import abc
+from typing import Sequence, Type
+
+from .. import mockbenchmark
+
+import crossbench as cb
+
+
+class BaseBenchmarkTestCase(
+    mockbenchmark.BaseCrossbenchTestCase, metaclass=abc.ABCMeta):
+
+  @property
+  @abc.abstractmethod
+  def benchmark_cls(self):
+    pass
+
+  @property
+  def story_cls(self):
+    return self.benchmark_cls.DEFAULT_STORY_CLS
+
+  def setUp(self):
+    super().setUp()
+    self.assertTrue(
+        issubclass(self.benchmark_cls, cb.benchmarks.Benchmark),
+        f"Expected Benchmark subclass, but got: BENCHMARK={self.benchmark_cls}")
+
+  def test_instantiate_no_stories(self):
+    with self.assertRaises(AssertionError):
+      self.benchmark_cls(stories=[])
+    with self.assertRaises(AssertionError):
+      self.benchmark_cls(stories="")
+    with self.assertRaises(AssertionError):
+      self.benchmark_cls(stories=["", ""])
+
+  def test_describe(self):
+    self.assertIsInstance(self.benchmark_cls.describe(), dict)
+
+
+class SubStoryTestCase(BaseBenchmarkTestCase, metaclass=abc.ABCMeta):
+
+  def test_stories_creation(self):
+    for name in self.story_cls.story_names():
+      stories = self.story_cls.from_names([name])
+      self.assertTrue(len(stories) == 1)
+      story = stories[0]
+      self.assertIsInstance(story, self.story_cls)
+      self.assertIsInstance(story.details_json(), dict)
+      self.assertTrue(len(str(story)) > 0)
+
+  def test_instantiate_no_stories(self):
+    with self.assertRaises(AssertionError):
+      self.benchmark_cls(stories=[])
+    with self.assertRaises(AssertionError):
+      self.benchmark_cls(stories="")
+    with self.assertRaises(AssertionError):
+      self.benchmark_cls(stories=["", ""])
+
+  def test_instantiate_single_story(self):
+    any_story_name = self.story_cls.story_names()[0]
+    any_story = self.story_cls.from_names([any_story_name])[0]
+    # Instantiate with single story,
+    with self.assertRaises(Exception):
+      self.benchmark_cls(any_story)
+    # with single story array
+    self.benchmark_cls([any_story])
+    with self.assertRaises(AssertionError):
+      # Accidentally nested array.
+      self.benchmark_cls([[any_story]])
+
+  def test_instantiate_all_stories(self):
+    stories = self.story_cls.from_names(self.story_cls.story_names())
+    self.benchmark_cls(stories)
+
+  def test_describe(self):
+    self.assertIsInstance(self.benchmark_cls.describe(), dict)
+
+
+
+class PressBaseBenchmarkTestCase(SubStoryTestCase, metaclass=abc.ABCMeta):
+
+  def test_invalid_story_names(self):
+    with self.assertRaises(Exception):
+      # Only one regexp entry will work
+      self.story_cls.from_names([".*", 'a'], separate=True)
diff --git a/tests/benchmarks/test_jetstream.py b/tests/benchmarks/test_jetstream.py
index d4af12c..ded3a04 100644
--- a/tests/benchmarks/test_jetstream.py
+++ b/tests/benchmarks/test_jetstream.py
@@ -5,10 +5,10 @@
 import crossbench as cb
 import crossbench.benchmarks as bm
 
-from . import BaseBenchmarkTestCase, PressBenchmarkTestCaseMixin
+from . import helper
 
 
-class JetStream2Test(BaseBenchmarkTestCase, PressBenchmarkTestCaseMixin):
+class JetStream2Test(helper.PressBaseBenchmarkTestCase):
 
   @property
   def benchmark_cls(self):
diff --git a/tests/benchmarks/test_loading.py b/tests/benchmarks/test_loading.py
index d62674c..b9e464a 100644
--- a/tests/benchmarks/test_loading.py
+++ b/tests/benchmarks/test_loading.py
@@ -5,10 +5,10 @@
 import crossbench as cb
 import crossbench.benchmarks as bm
 
-from . import BaseBenchmarkTestCase
+from . import helper
 
 
-class TestPageLoadBenchmark(BaseBenchmarkTestCase):
+class TestPageLoadBenchmark(helper.SubStoryTestCase):
 
   @property
   def benchmark_cls(self):
diff --git a/tests/benchmarks/test_motionmark.py b/tests/benchmarks/test_motionmark.py
index efcbc4e..88800cc 100644
--- a/tests/benchmarks/test_motionmark.py
+++ b/tests/benchmarks/test_motionmark.py
@@ -5,10 +5,10 @@
 import crossbench as cb
 import crossbench.benchmarks as bm
 
-from . import BaseBenchmarkTestCase, PressBenchmarkTestCaseMixin
+from . import helper
 
 
-class MotionMark2Test(BaseBenchmarkTestCase, PressBenchmarkTestCaseMixin):
+class MotionMark2Test(helper.PressBaseBenchmarkTestCase):
 
   @property
   def benchmark_cls(self):
diff --git a/tests/benchmarks/test_speedometer.py b/tests/benchmarks/test_speedometer.py
index 78c4d5d..554a4ea 100644
--- a/tests/benchmarks/test_speedometer.py
+++ b/tests/benchmarks/test_speedometer.py
@@ -5,10 +5,11 @@
 import crossbench as cb
 import crossbench.benchmarks as bm
 
-from . import BaseBenchmarkTestCase, PressBenchmarkTestCaseMixin
+from . import helper
 
 
-class Speedometer2Test(BaseBenchmarkTestCase, PressBenchmarkTestCaseMixin):
+class Speedometer2Test(helper.PressBaseBenchmarkTestCase):
+
 
   @property
   def benchmark_cls(self):
diff --git a/tests/mockbenchmark.py b/tests/mockbenchmark.py
index 583d673..7190d94 100644
--- a/tests/mockbenchmark.py
+++ b/tests/mockbenchmark.py
@@ -47,7 +47,7 @@
 
 
 class MockBrowser(cb.browsers.Browser):
-  BIN_PATH = None
+  BIN_PATH: pathlib.Path = pathlib.Path("/")
   VERSION = "100.22.33.44"
 
   @classmethod
@@ -69,11 +69,11 @@
     super().__init__(label, path, *args, **kwargs)
     self.url_list: List[str] = []
     self.js_list: List[str] = []
-    self.js_side_effect = []
-    self.run_js_side_effect = []
-    self.did_run = False
-    self.clear_cache_dir = False
-    self.js_flags = cb.flags.JSFlags()
+    self.js_side_effect: List[str] = []
+    self.run_js_side_effect: List[str] = []
+    self.did_run: bool = False
+    self.clear_cache_dir: bool = False
+    self.js_flags: cb.flags.JSFlags = cb.flags.JSFlags()
 
   def clear_cache(self, runner: cb.runner.Runner):
     pass