1# Copyright 2015 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import re
6
7from telemetry.story import shared_state as shared_state_module
8
9_next_story_id = 0
10
11
12class Story(object):
13  """A class styled on unittest.TestCase for creating story tests.
14
15  Tests should override Run to maybe start the application and perform actions
16  on it. To share state between different tests, one can define a
17  shared_state which contains hooks that will be called before and
18  after mutiple stories run and in between runs.
19
20  Args:
21    shared_state_class: subclass of telemetry.story.shared_state.SharedState.
22    name: string name of this story that can be used for identifying this story
23        in results output.
24    labels: A list or set of string labels that are used for filtering. See
25        story.story_filter for more information.
26    is_local: If True, the story does not require network.
27  """
28
29  def __init__(self, shared_state_class, name='', labels=None,
30               is_local=False, make_javascript_deterministic=True):
31    """
32    Args:
33      make_javascript_deterministic: Whether JavaScript performed on
34          the page is made deterministic across multiple runs. This
35          requires that the web content is served via Web Page Replay
36          to take effect. This setting does not affect stories containing no web
37          content or where the HTTP MIME type is not text/html.See also:
38          _InjectScripts method in third_party/webpagereplay/httpclient.py.
39    """
40    assert issubclass(shared_state_class,
41                      shared_state_module.SharedState)
42    self._shared_state_class = shared_state_class
43    self._name = name
44    global _next_story_id
45    self._id = _next_story_id
46    _next_story_id += 1
47    if labels is None:
48      labels = set([])
49    elif isinstance(labels, list):
50      labels = set(labels)
51    else:
52      assert isinstance(labels, set)
53    self._labels = labels
54    self._is_local = is_local
55    self._make_javascript_deterministic = make_javascript_deterministic
56
57  def Run(self, shared_state):
58    """Execute the interactions with the applications and/or platforms."""
59    raise NotImplementedError
60
61  @property
62  def labels(self):
63    return self._labels
64
65  @property
66  def shared_state_class(self):
67    return self._shared_state_class
68
69  @property
70  def id(self):
71    return self._id
72
73  @property
74  def name(self):
75    return self._name
76
77  def AsDict(self):
78    """Converts a story object to a dict suitable for JSON output."""
79    d = {
80      'id': self._id,
81    }
82    if self._name:
83      d['name'] = self._name
84    return d
85
86  @property
87  def file_safe_name(self):
88    """A version of display_name that's safe to use as a filename.
89
90    The default implementation sanitizes special characters with underscores,
91    but it's okay to override it with a more specific implementation in
92    subclasses.
93    """
94    # This fail-safe implementation is safe for subclasses to override.
95    return re.sub('[^a-zA-Z0-9]', '_', self.display_name)
96
97  @property
98  def display_name(self):
99    if self.name:
100      return self.name
101    else:
102      return self.__class__.__name__
103
104  @property
105  def is_local(self):
106    """Returns True iff this story does not require network."""
107    return self._is_local
108
109  @property
110  def serving_dir(self):
111    """Returns the absolute path to a directory with hash files to data that
112       should be updated from cloud storage, or None if no files need to be
113       updated.
114    """
115    return None
116
117  @property
118  def make_javascript_deterministic(self):
119    return self._make_javascript_deterministic
120