blob: 6bbb8e0bacc67ec2bc123a7b4a5f174f32e1f178 [file] [log] [blame]
Joel Kitching58256632016-11-15 15:47:42 +08001# Copyright 2016 The Chromium OS 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
5"""A collection of mock classes for plugin unittests."""
6
7# TODO(kitching): Add locks to ensure multi-threading support.
Joel Kitching58256632016-11-15 15:47:42 +08008
9from __future__ import print_function
10
chuntsen71a92512017-04-20 14:10:45 +080011import copy
Joel Kitching58256632016-11-15 15:47:42 +080012import logging
Joel Kitching94050142017-01-30 12:20:04 +080013import os
14import shutil
15import tempfile
Joel Kitching58256632016-11-15 15:47:42 +080016
Wei-Han Chend665b002019-10-03 16:16:09 +080017from cros.factory.instalog import plugin_base
18from cros.factory.instalog import plugin_sandbox
19from cros.factory.instalog.utils import file_utils
Joel Kitching58256632016-11-15 15:47:42 +080020
21
22class MockCore(plugin_sandbox.CoreAPI):
23 """Implements CoreAPI as a mock object for testing.
24
25 Allows low-level access to BufferEventStreams, as well as storing and playing
26 back Emit call history.
27
28 BufferEventStreams: Any number of BufferEventStreams can be created. For
29 example, if the plugin is single-threaded, one BufferEventStream is
30 sufficient. However, if you specifically want to separate Events into two
31 batches that are separately read by the plugin as separate BufferEventStreams,
32 you can create two BufferEventStream objects. Usage: Use Queue to add Events
33 to the MockBufferEventStream object returned by GetStream. Example:
34
35 mock_core.GetStream(0).Queue([datatypes.Event({})])
36
37 Emit call history: Stored in the self.emit_calls instance variable as a list.
38 The Event list from each call is appended to the emit_calls list every time
39 Emit is called. Example:
40
41 self.assertEqual(mock_core.emit_calls[0], [datatypes.Event({})])
42 """
43
44 def __init__(self):
chuntsene014a2c2017-03-17 14:02:49 +080045 self._att_dir = tempfile.mkdtemp(prefix='instalog_testing_')
Joel Kitching58256632016-11-15 15:47:42 +080046 self.emit_calls = []
47 self.streams = []
48
Joel Kitching94050142017-01-30 12:20:04 +080049 def Close(self):
50 """Performs any final operations."""
51 shutil.rmtree(self._att_dir)
52
Joel Kitchinge1e276f2016-12-13 13:05:33 +080053 def AllStreamsExpired(self):
54 """Returns True if all streams are currently expired."""
55 return all([stream.expired for stream in self.streams])
56
Joel Kitching58256632016-11-15 15:47:42 +080057 def Emit(self, plugin, events):
58 """Stores the events from this Emit call into self.emit_calls."""
59 del plugin
Joel Kitching94050142017-01-30 12:20:04 +080060 for event in events:
61 # Move attachments to a temporary directory to simulate buffer.
Yilin Yang879fbda2020-05-14 13:52:30 +080062 for att_id, att_path in event.attachments.items():
Joel Kitching94050142017-01-30 12:20:04 +080063 # Use a filename that contains the original one for clarity.
Hung-Te Lin3afccc72017-06-01 22:37:33 +080064 tmp_path = file_utils.CreateTemporaryFile(
chuntsene014a2c2017-03-17 14:02:49 +080065 prefix=os.path.basename(att_path) + '_', dir=self._att_dir)
Joel Kitching94050142017-01-30 12:20:04 +080066 # Relocate the attachment and update the event path.
67 logging.debug('Moving attachment %s --> %s...', att_path, tmp_path)
68 shutil.move(att_path, tmp_path)
69 event.attachments[att_id] = tmp_path
Joel Kitching58256632016-11-15 15:47:42 +080070 self.emit_calls.append(events)
Joel Kitching043db1a2017-01-29 14:52:03 +080071 return True
Joel Kitching58256632016-11-15 15:47:42 +080072
73 def GetStream(self, stream_id):
74 """Retrieves the stream with the given ID, creating if necessary."""
75 assert stream_id >= 0 and stream_id <= len(self.streams)
76 if stream_id < len(self.streams):
77 return self.streams[stream_id]
Yilin Yang15a3f8f2020-01-03 17:49:00 +080078 stream = MockBufferEventStream()
79 self.streams.append(stream)
80 return stream
Joel Kitching58256632016-11-15 15:47:42 +080081
82 def NewStream(self, plugin):
83 """Returns the next available EventStream (with Events in it)."""
84 del plugin
85 ret_stream = None
86 # First, look for an expired stream with events in it.
87 for stream in self.streams:
Joel Kitching75fe8b32016-11-25 13:11:49 +080088 if stream.expired and not stream.Empty():
Joel Kitching58256632016-11-15 15:47:42 +080089 ret_stream = stream
90
91 # Next, fall back to an expired stream without events.
92 if not ret_stream:
93 for stream in self.streams:
94 if stream.expired:
95 ret_stream = stream
96
97 # Finally, if all streams are in use, create a new one.
98 if not ret_stream:
99 ret_stream = self.GetStream(len(self.streams))
100
101 # Set to expired and return.
102 ret_stream.expired = False
103 logging.debug('NewStream returns: %s', ret_stream)
104 return ret_stream
105
Joel Kitching75fe8b32016-11-25 13:11:49 +0800106 def GetProgress(self, plugin):
107 """Returns the progress of the plugin through available events.
108
109 Normally returns a tuple (completed_count, total_count), but for testing we
110 don't require such a fine granularity of information. Thus, we will simply
111 return (0, 1) for incomplete, and (1, 1) for complete. Completion is
112 defined by all streams being empty.
113 """
114 del plugin
115 for stream in self.streams:
116 if not stream.Empty():
117 return 0, 1
118 return 1, 1
119
Joel Kitching8c3d31a2016-12-12 16:09:48 +0800120 def GetNodeID(self):
121 """Returns a fake node ID."""
122 return 'testing'
123
Joel Kitching58256632016-11-15 15:47:42 +0800124
125class MockBufferEventStream(plugin_base.BufferEventStream):
126 """Implements a mock BufferEventStream class."""
127
128 def __init__(self):
129 self.expired = True
Joel Kitching75fe8b32016-11-25 13:11:49 +0800130 self.queue = []
131 self.consumed = []
Joel Kitching58256632016-11-15 15:47:42 +0800132
133 def Queue(self, events):
Joel Kitching75fe8b32016-11-25 13:11:49 +0800134 """Queues the supplied events."""
135 logging.debug('%s: Pushing %d events...', self, len(events))
136 self.queue.extend(events)
137
138 def Empty(self):
139 """Returns whether or not there are events in this EventStream."""
140 return len(self.queue) == 0 and len(self.consumed) == 0
Joel Kitching58256632016-11-15 15:47:42 +0800141
142 def Next(self):
Joel Kitching75fe8b32016-11-25 13:11:49 +0800143 """Pops the next available Event or returns None if not available."""
Joel Kitching58256632016-11-15 15:47:42 +0800144 if self.expired:
145 raise plugin_base.EventStreamExpired
Peter Shih19a938f2018-02-26 14:26:16 +0800146 if not self.queue:
Joel Kitching58256632016-11-15 15:47:42 +0800147 logging.debug('%s: Nothing to pop', self)
148 return None
Joel Kitching75fe8b32016-11-25 13:11:49 +0800149 ret = self.queue.pop(0)
150 self.consumed.append(ret)
Joel Kitching94050142017-01-30 12:20:04 +0800151 logging.debug('%s: Popping next event...', self)
chuntsen71a92512017-04-20 14:10:45 +0800152 return copy.deepcopy(ret)
Joel Kitching58256632016-11-15 15:47:42 +0800153
154 def Commit(self):
Joel Kitching75fe8b32016-11-25 13:11:49 +0800155 """Marks the EventStream as committed and expired."""
Joel Kitching58256632016-11-15 15:47:42 +0800156 if self.expired:
157 raise plugin_base.EventStreamExpired
Joel Kitching75fe8b32016-11-25 13:11:49 +0800158 self.consumed = []
Joel Kitching58256632016-11-15 15:47:42 +0800159 self.expired = True
160
161 def Abort(self):
Joel Kitching75fe8b32016-11-25 13:11:49 +0800162 """Marks the EventStream as aborted and expired."""
Joel Kitching58256632016-11-15 15:47:42 +0800163 if self.expired:
164 raise plugin_base.EventStreamExpired
Joel Kitching75fe8b32016-11-25 13:11:49 +0800165 self.queue = self.consumed + self.queue
166 self.consumed = []
Joel Kitching58256632016-11-15 15:47:42 +0800167 self.expired = True