def test_logger_yes_remove(self): new_log_file = f'{TESTS_FOLDER_NAME}/{self._testMethodName}.log' order_book = OrderBook(new_log_file) bid = self.create_bid(100, 5) order_book.add_order(bid, random_str()) ask = self.create_ask(90, 7) order_book.add_order(ask, random_str()) with self.assertRaises(KeyError) as e: order_book.remove_order(bid.id, bid.sender_id) err_regex = r'^Tried to remove order but no such order exists\.\\nOrder id: \d{15}\. Order key: \(100, 5\)$' self.assertRegex(e.args[0], err_regex) order_book.remove_order(ask.id, ask.sender_id) with open(new_log_file, 'r') as f: lines = f.readlines() # the data in the now created log file is expected to match the following regex expressions regex = list(map(re.compile, [ r'BID \| timestamp: \d{10}\.\d{5,7}, side: b, price: 100, size: 5, id: \d{15}, sender_id: "[a-zA-Z0-9]{8}"', r'ASK \| timestamp: \d{10}\.\d{5,7}, side: a, price: 90, size: 7, id: \d{15}, sender_id: "[a-zA-Z0-9]{8}"', r'TRADE \| timestamp: \d{10}\.\d{5,7}, price: 100, size: 5, buyer_id: "[a-zA-Z0-9]{8}", seller_id: "[a-zA-Z0-9]{8}"', r'\t--> ASK id: \d{15} now has size: 2', r'\t--> BID id: \d{15} now has been exhausted', r'ASK (rm) | timestamp: \d{10}\.\d{5,7}, side: a, price: 90, size: 2, id: \d{15}, sender_id: "[a-zA-Z0-9]{8}"' ] )) for i, line in enumerate(lines): self.assertRegex(line, regex[i])
def kml_delete(self, kml_id): session = self.beaker with self.session() as s: geo_layer = s.query(GeoLayer).filter_by(id=int(kml_id)).first() if not geo_layer: raise HTTPResponse(status=404, body='404 Not Found') if request.method == 'POST': if session['csrf'] != request.params.get('csrf', ''): return redirect('/') confirmed = request.params.get('confirmed', None) if not confirmed: session['csrf'] = random_str(100) return self._render( 'kml_delete.html.j2', geo_layer=geo_layer, error=u'チェックしてください', session=session ) s.delete(geo_layer) s.commit() return redirect('/kml/') else: session['csrf'] = random_str(100) return self._render('kml_delete.html.j2', geo_layer=geo_layer, session=session)
def kml_index(self): session = self.beaker print 'kml_index' err, suc = [], [] kml_layer_name = '' with self.session() as s: try: if request.method == 'POST': csrf = request.params.get('csrf', '') if session.get('csrf', '') != csrf: print 'csrf token mismatch' r = HTTPResponse(status=302) r.set_header('Location: /kml/') raise r print 'method = POST' name = request.params.get('name', '') name = name.decode('utf-8') upload = request.files.get('kml_file', '') if not name: err.append(u'KMLレイヤーの名前を入力してください') else: kml_layer_name = name if not upload: err.append(u'ファイルがアップロードされていません') if not err: tmp_fname = '/tmp/minarepo_kml_%s' % random_str(40) upload.save(tmp_fname) try: with open(tmp_fname, 'rb') as fh: fdata = fh.read() geo_layer = GeoLayer( name=name, content=fdata, file_size=len(fdata) ) s.add(geo_layer) finally: os.remove(tmp_fname) suc.append(u'KMLレイヤーを作成しました: %s' % name) kml_layer_name = '' layers = s.query(GeoLayer).order_by(GeoLayer.created.desc()) layers = [ l.to_api_dict() for l in layers ] except: s.rollback() raise else: s.commit() print 'commited' session['csrf'] = random_str(100) return self._render('kml.html.j2', err=err, suc=suc, layers=layers, kml_layer_name=kml_layer_name, session=self.beaker)
def random_mount(): properties = {} for _ in range(random.randint(1, 10)): properties[random_str()] = random_str() return option.Mount(properties=properties, read_only=random_bool(), shared=random_bool())
def random_file_info(): block_ids = [] for _ in range(random.randint(1, 10)): block_ids.append(random_int()) file_block_infos = [] for _ in range(random.randint(1, 10)): file_block_infos.append(random_file_block_info()) return wire.FileInfo(block_ids=block_ids, block_size_bytes=random_int(), cacheable=random_bool(), completed=random_bool(), creation_time_ms=random_int(), file_block_infos=file_block_infos, file_id=random_int(), folder=random_bool(), group=random_str(), in_memory_percentage=random_int(), last_modification_time_ms=random_int(), length=random_int(), mode=random_int(), mount_point=random_bool(), name=random_str(), owner=random_str(), path=random_str(), persisted=random_bool(), persistence_state=random_persistence_state(), pinned=random_bool(), ttl=random_int(), ttl_action=random_ttl_action(), ufs_path=random_str())
def kml_delete(self, kml_id): session = self.beaker with self.session() as s: geo_layer = s.query(GeoLayer).filter_by(id=int(kml_id)).first() if not geo_layer: raise HTTPResponse(status=404, body='404 Not Found') if request.method == 'POST': if session['csrf'] != request.params.get('csrf', ''): return redirect('/') confirmed = request.params.get('confirmed', None) if not confirmed: session['csrf'] = random_str(100) return self._render('kml_delete.html.j2', geo_layer=geo_layer, error=u'チェックしてください', session=session) s.delete(geo_layer) s.commit() return redirect('/kml/') else: session['csrf'] = random_str(100) return self._render('kml_delete.html.j2', geo_layer=geo_layer, session=session)
def random_set_attribute(): return option.SetAttribute(group=random_str(), mode=random_mode(), owner=random_str(), persisted=random_bool(), pinned=random_bool(), recursive=random_bool(), ttl=random_int(), ttl_action=random_ttl_action())
def counter(counter_id=None): response = { 'status': 'OK' } if request.method == 'POST': data = request.get_json() text_after = data['text_after'] text_before = data['text_before'] # The time in UTC time = dateutil.parser.parse(data['time']) theme = data['theme'] counter = None with db.session.no_autoflush: # The unlikely event of violationg the unique constraint for url # is not handled in any way. counter = Counter(url=random_str(), theme=theme, secret=random_str(), text_before=text_before, text_after=text_after, time=time) if theme == 'trip': origin = data['city_origin'] destination = data['city_destination'] trip_theme = TripTheme(counter=counter, origin=origin, destination=destination) db.session.add(trip_theme) db.session.add(counter) db.session.commit() elif request.method== 'GET': if counter_id is not None: print 'counter_id', counter_id counter = Counter.query.get_or_404(counter_id) response['data'] = counter.to_dict() else: # Filter out counters url = request.args.get('url', None) if url is None: abort(404) counters = Counter.query.filter_by(url=url).all() response['data'] = map(lambda x: x.to_dict(), counters) return jsonify(**response)
def _post(self): if self._usr: upload_path = os.path.join(os.path.dirname(__file__), '../cache') file_metas = self.request.files.get('file', None) if not file_metas: return cfg.mis.code, { 'err': cfg.mis.text, } files_list = [] for meta in file_metas: filename = random_str(10) + meta['filename'] file_path = os.path.join(upload_path, filename) try: print(file_path) with open(file_path, 'wb+') as up: up.write(meta['body']) files_list.append('./static/' + filename) except Exception as e: return cfg.mis.code, { 'err': str(e), } return cfg.suc.code, { 'msg': 'upload successed', 'files_list': files_list, } else: return cfg.aut.code, { 'err': cfg.aut.text, }
def random_file_block_info(): ufs_locations = [] for _ in range(random.randint(1, 10)): ufs_locations.append(random_str()) return wire.FileBlockInfo(block_info=random_block_info(), offset=random_int(), ufs_locations=ufs_locations)
def create_mock_orderbook(self, logfile_full_path): """ Creates a mock orderbook which contains 4 bids (price, size): - (100, 10) - (70, 15) - (99, 7) - (101, 9) These hardcoded values are tested throughout the testing functions, don't change """ order_book = OrderBook(logfile_full_path) order_book.add_order(self.create_bid(100, 10), sender_id=random_str()) order_book.add_order(self.create_bid(70, 15), random_str()) order_book.add_order(self.create_bid(99, 7), random_str()) order_book.add_order(self.create_bid(101, 9), random_str()) return order_book
def test_add_ask_yes_bid_exhaust(self): order_book = self.create_mock_orderbook(f'{TESTS_FOLDER_NAME}/{self._testMethodName}.log') _, min_bid_before = order_book.bids.min_item() _, max_bid_before = order_book.bids.max_item() self.assertEqual(order_book.bids.count, 4) self.assertEqual(order_book.asks.count, 0) self.assertEqual(min_bid_before.size, 15) self.assertEqual(min_bid_before.price, 70) self.assertEqual(max_bid_before.size, 9) self.assertEqual(max_bid_before.price, 101) order_book.add_order(self.create_ask(99, 26), random_str()) self.assertEqual(order_book.bids.count, 1) self.assertEqual(order_book.asks.count, 0) _, min_bid_after = order_book.bids.min_item() self.assertEqual(min_bid_after.size, 15) self.assertEqual(min_bid_after.price, 70) self.assertEqual(min_bid_after, min_bid_before) self.assertIsNone(max_bid_before.size) self.assertTrue(max_bid_before.is_exhausted()) _, max_bid_after = order_book.bids.max_item() self.assertEqual(max_bid_after, min_bid_after) self.assertNotEqual(max_bid_after, max_bid_before)
def _authenticated_remote(self, use_ssh=True): if use_ssh: url = urljoin(self.github_cfg.ssh_url(), self.github_repo_path) tmp_id = os.path.abspath('tmp.id_rsa') with open(tmp_id, 'w') as f: f.write(self.github_cfg.credentials().private_key()) os.chmod(tmp_id, 0o400) suppress_hostcheck = '-o "StrictHostKeyChecking no"' id_only = '-o "IdentitiesOnly yes"' os.environ[ 'GIT_SSH_COMMAND'] = f'ssh -i {tmp_id} {suppress_hostcheck} {id_only}' else: url = url_with_credentials(self.github_cfg, self.github_repo_path) remote = git.remote.Remote.add( repo=self.repo, name=random_str(), url=url, ) try: yield remote finally: self.repo.delete_remote(remote) if use_ssh: os.unlink(tmp_id) del os.environ['GIT_SSH_COMMAND']
def test_mount(): path = '/foo' src = random_str() option = random_mount() client, cleanup = setup_client( paths_handler(path, 'mount', params={'src': src}, input=option)) client.mount(path, src, option) cleanup()
def test_logger_no_remove(self): new_log_file = f'{TESTS_FOLDER_NAME}/{self._testMethodName}.log' order_book = OrderBook(new_log_file) order_book.add_order(self.create_bid(100, 5), random_str()) order_book.add_order(self.create_ask(90, 7), random_str()) with open(new_log_file, 'r') as f: lines = f.readlines() # the data in the now created log file is expected to match the following regex expressions regex = [ r'BID \| timestamp: \d{10}\.\d{5,7}, side: b, price: 100, size: 5, id: \d{15}, sender_id: "[a-zA-Z0-9]{8}"', r'ASK \| timestamp: \d{10}\.\d{5,7}, side: a, price: 90, size: 7, id: \d{15}, sender_id: "[a-zA-Z0-9]{8}"', r'TRADE \| timestamp: \d{10}\.\d{5,7}, price: 100, size: 5, buyer_id: "[a-zA-Z0-9]{8}", seller_id: "[a-zA-Z0-9]{8}"', r'\t--> ASK id: \d{15} now has size: 2', r'\t--> BID id: \d{15} now has been exhausted', ] for i, line in enumerate(lines): self.assertRegex(line, regex[i])
def test_read(): file_id = random_int() message = random_str() client, cleanup = setup_client( streams_handler(file_id, 'read', output=message)) reader = client.read(file_id) got = reader.read() reader.close() assert got == message
def test_write(): file_id = random_int() message = random_str() client, cleanup = setup_client( streams_handler(file_id, 'write', input=message)) writer = client.write(file_id) length = writer.write(message) writer.close() assert length == len(message)
def random_create_file(): return option.CreateFile(block_size_bytes=random_int(), location_policy_class=random_str(), mode=random_mode(), recursive=random_bool(), ttl=random_int(), ttl_action=random_ttl_action(), write_type=random_write_type(), replication_durable=random_int(), replication_max=random_int(), replication_min=random_int())
def test_show_trades(self): import time order_book = self.create_mock_orderbook(f'{TESTS_FOLDER_NAME}/{self._testMethodName}.log') for _ in range(26): order_book.add_order(self.create_ask(99, 1), random_str()) time.sleep(0.01) trades = order_book.show_trades() trades_are_timestamp_sorted = all( [trade.timestamp < trades[i + 1].timestamp for i, trade in enumerate(trades[:-1])]) self.assertTrue(trades_are_timestamp_sorted)
def test_open_write(): path = '/foo' file_id = random_int() message = random_str() handler = combined_handler(path, 'create-file', file_id, 'write', path_output=file_id, stream_input=message) client, cleanup = setup_client(handler) written_len = None with client.open(path, 'w') as f: written_len = f.write(message) cleanup() assert written_len == len(message)
def test_open_read(): path = '/foo' file_id = random_int() message = random_str() handler = combined_handler( path, 'open-file', file_id, 'read', path_output=file_id, stream_output=message) client, cleanup = setup_client(handler) got = None with client.open(path, 'r') as f: got = f.read() cleanup() assert got == message.encode()
def win_base_idx(): """get the tmux's win-base-idx option, if no server was started, create a dummy session, the session needs to be removed after restoring""" global WIN_BASE_IDX if WIN_BASE_IDX == None: if not cmd.has_tmux_server(): global DUMMY_SESSION DUMMY_SESSION = util.random_str(10) LOG.debug('Create Dummy session:%s'%DUMMY_SESSION) cmd.create_session(DUMMY_SESSION, '[10,10]') WIN_BASE_IDX = int(cmd.get_option('base-index')) return WIN_BASE_IDX
def win_base_idx(): """get the tmux's win-base-idx option, if no server was started, create a dummy session, the session needs to be removed after restoring""" global WIN_BASE_IDX if WIN_BASE_IDX == None: if not cmd.has_tmux_server(): global DUMMY_SESSION DUMMY_SESSION = util.random_str(10) LOG.debug('Create Dummy session:%s' % DUMMY_SESSION) cmd.create_session(DUMMY_SESSION, '[10,10]') WIN_BASE_IDX = int(cmd.get_option('base-index')) return WIN_BASE_IDX
def _authenticated_remote(self, use_ssh=False): if use_ssh: url = urljoin(self.github_cfg.ssh_url(), self.github_repo_path) else: url = url_with_credentials(self.github_cfg, self.github_repo_path) remote = git.remote.Remote.add( repo=self.repo, name=random_str(), url=url, ) try: yield remote finally: self.repo.delete_remote(remote)
def kml_update(self, kml_id): session = self.beaker with self.session() as s: geo_layer = s.query(GeoLayer).filter_by(id=int(kml_id)).first() if not geo_layer: raise HTTPResponse(status=404, body='404 Not Found') if request.method == 'POST': if session['csrf'] != request.params.get('csrf', ''): return redirect('/') errors = [] name = request.params.get('name', '') name = name.decode('utf-8') if not name: errors.append(u'KMLレイヤーの名前が指定されていません') if len(errors) == 0: upload = request.files.get('kml_file', '') if upload: tmp_fname = '/tmp/minarepo_kml_%s' % random_str(40) try: upload.save(tmp_fname) with open(tmp_fname, 'rb') as fh: fdata = fh.read() geo_layer.content = fdata geo_layer.file_size = len(fdata) finally: os.remove(tmp_fname) geo_layer.name = name s.add(geo_layer) s.commit() return redirect('/kml/') else: return self._render('kml_update.html.j2', geo_layer=geo_layer, session=session, errors=errors) return self._render('kml_update.html.j2', geo_layer=geo_layer, session=session)
def _authenticated_remote(self, use_ssh=True): if use_ssh: url = urljoin(self.github_cfg.ssh_url(), self.github_repo_path) tmp_id = _ssh_auth_env(github_cfg=self.github_cfg) else: url = url_with_credentials(self.github_cfg, self.github_repo_path) remote = git.remote.Remote.add( repo=self.repo, name=random_str(), url=url, ) try: yield remote finally: self.repo.delete_remote(remote) if use_ssh: os.unlink(tmp_id) del os.environ['GIT_SSH_COMMAND']
def kml_update(self, kml_id): session = self.beaker with self.session() as s: geo_layer = s.query(GeoLayer).filter_by(id=int(kml_id)).first() if not geo_layer: raise HTTPResponse(status=404, body='404 Not Found') if request.method == 'POST': if session['csrf'] != request.params.get('csrf', ''): return redirect('/') errors = [] name = request.params.get('name', '') name = name.decode('utf-8') if not name: errors.append(u'KMLレイヤーの名前が指定されていません') if len(errors) == 0: upload = request.files.get('kml_file', '') if upload: tmp_fname = '/tmp/minarepo_kml_%s' % random_str(40) try: upload.save(tmp_fname) with open(tmp_fname, 'rb') as fh: fdata = fh.read() geo_layer.content = fdata geo_layer.file_size= len(fdata) finally: os.remove(tmp_fname) geo_layer.name = name s.add(geo_layer) s.commit() return redirect('/kml/') else: return self._render( 'kml_update.html.j2', geo_layer=geo_layer, session=session, errors=errors ) return self._render('kml_update.html.j2', geo_layer=geo_layer, session=session)
def test_random(): s = '\n' s += 'random_int(1) = ' + str(util.random_int(1)) + '\n' s += '\n' s += 'random_int(10) = ' + str(util.random_int(10)) + '\n' s += '\n' s += 'random_int(5, 20) = ' + str(util.random_int(5, 20)) + '\n' s += '\n' s += 'random_int(a, b, step) = ' + str(util.random_int(10, 20, 5)) + '\n' s += '\n' s += 'random_float(5, 10) = ' + str(util.random_float(5, 10)) + '\n' s += 'random_float(-1, 1) = ' + str(util.random_float(-1, 1)) + '\n' s += 'random_float(0, 1) = ' + str(util.random_float(0, 1)) + '\n' s += 'random_float(1.9, 2) = ' + str(util.random_float(1.9, 2)) + '\n' s += 'random_float(0, 1.1) = ' + str(util.random_float(0, 1.1)) + '\n' s += '\n' s += 'random_bool() = ' + str(util.random_bool()) + '\n' s += 'random_bool(0) = ' + str(util.random_bool(0)) + '\n' s += 'random_bool(1/10) = ' + str(util.random_bool(1 / 10)) + '\n' s += 'random_bool(1/90) = ' + str(util.random_bool(90 / 100)) + '\n' s += 'random_bool(100/100) = ' + str(util.random_bool(100 / 100)) + '\n' s += 'random_bool(2) = ' + str(util.random_bool(2)) + '\n' s += '\n' s += 'random_ascii() = ' + util.random_ascii() + '\n' s += '\n' s += 'random_str(4) = "' + util.random_str(4) + '"\n' s += '\n' s += 'random_str(1, 3) = "' + util.random_str(1, 3) + '"\n' s += '\n' s += 'random_str(4, tbl=\'ABC123\') = "' + util.random_str( 4, tbl='ABC123') + '"\n' s += '\n' s += 'random_str(1, 3, tbl=\'ABC123\') = "' + util.random_str( 1, 3, tbl='ABC123') + '"\n' s += '\n' s += 'random_str(4, tbl=\'ABC123あいうえお\') = "' + util.random_str( 4, tbl='ABC123あいうえお') + '"\n' s += '\n' s += 'random_str(1, 3, tbl=\'ABC123あいうえお\') = "' + util.random_str( 1, 3, tbl='ABC123あいうえお') + '"\n' return s
def test_add_ask_no_bid_exhaust(self): order_book = self.create_mock_orderbook(f'{TESTS_FOLDER_NAME}/{self._testMethodName}.log') min_bid_key, min_bid = order_book.bids.min_item() max_bid_key, max_bid = order_book.bids.max_item() self.assertEqual(order_book.bids.count, 4) self.assertTrue(order_book.asks.is_empty()) self.assertEqual(min_bid.size, 15) self.assertEqual(min_bid.price, 70) self.assertEqual(max_bid.size, 9) self.assertEqual(max_bid.price, 101) order_book.add_order(self.create_ask(99, 8), random_str()) self.assertEqual(order_book.bids.count, 4) self.assertTrue(order_book.asks.is_empty()) self.assertEqual(min_bid.size, 15) self.assertEqual(min_bid.price, 70) self.assertEqual(max_bid.size, 1) self.assertEqual(max_bid.price, 101)
def test_remove_order(self): order_book = self.create_mock_orderbook(f'{TESTS_FOLDER_NAME}/{self._testMethodName}.log') ask = self.create_ask(100, 30) order_book.add_order(ask, random_str()) self.assertEqual(order_book.asks.count, 1) self.assertEqual(order_book.bids.count, 3) order_book.remove_order(ask.id, ask.sender_id) self.assertEqual(order_book.asks.count, 0) # Copy the values, so removing their references won't interfere with testing bids_copy = list(order_book.bids.values()) bids_ids = [bid.id for bid in bids_copy] for bid_id in bids_ids: # Should not remove orders with no matching sender id. order_book.remove_order(bid_id, "BAD SENDER ID") self.assertEqual(order_book.bids.count, 3) # no bids_copy were removed for bid in bids_copy: # Should remove order_book.remove_order(bid.id, bid.sender_id) self.assertEqual(order_book.bids.count, 0)
def test_show_top(self): new_log_file = f'{TESTS_FOLDER_NAME}/{self._testMethodName}.log' order_book = OrderBook(new_log_file) order_book.add_order(self.create_ask(100, 30), random_str()) order_book.add_order(self.create_ask(100, 29), random_str()) order_book.add_order(self.create_bid(10, 3), random_str()) order_book.add_order(self.create_bid(10, 4), random_str()) highest_bid, lowest_ask = order_book.show_top() # Test overridden Order comparison functions (by _key()) self.assertTrue((highest_bid.price, highest_bid.size), (10, 4)) self.assertTrue((lowest_ask.price, lowest_ask.size), (100, 29)) order_book.add_order(self.create_ask(99, 28), random_str()) order_book.add_order(self.create_bid(11, 5), random_str()) highest_bid, lowest_ask = order_book.show_top() self.assertTrue((highest_bid.price, highest_bid.size), (11, 5)) self.assertTrue((lowest_ask.price, lowest_ask.size), (99, 28))
return max_lcs # x = 'thecatruns' # y = 'acatran' # z = LCS_BRUTE_FORCE(x,y) # q = LCS_DRIVER(x,y) # print(z,q) for n in range(10,3000,300): x = util.random_str(n) y = util.random_str(n) start = time.time() ans = LCS_BRUTE_FORCE(x,y) end = time.time() print("brute force:",end-start,ans) start = time.time() LCS_TABLE = {} ans = LCS_DRIVER(x,y) end = time.time() print("DP:",end-start,ans) print('')
def random_open_file(): return option.OpenFile(cache_location_policy_class=random_str(), ufs_read_location_policy_class=random_str(), max_ufs_read_concurrency=random_int(), read_type=random_read_type())
def random_block_location(): return wire.BlockLocation(worker_id=random_int(), worker_address=random_worker_net_address(), tier_alias=random_str())
def __init__(self, dba): self._dba = dba self._fname = '/tmp/mrv_' + random_str(12) + '.csv' self._finished = False
def kml_index(self): session = self.beaker print 'kml_index' err, suc = [], [] kml_layer_name = '' with self.session() as s: try: if request.method == 'POST': csrf = request.params.get('csrf', '') if session.get('csrf', '') != csrf: print 'csrf token mismatch' r = HTTPResponse(status=302) r.set_header('Location: /kml/') raise r print 'method = POST' name = request.params.get('name', '') name = name.decode('utf-8') upload = request.files.get('kml_file', '') if not name: err.append(u'KMLレイヤーの名前を入力してください') else: kml_layer_name = name if not upload: err.append(u'ファイルがアップロードされていません') if not err: tmp_fname = '/tmp/minarepo_kml_%s' % random_str(40) upload.save(tmp_fname) try: with open(tmp_fname, 'rb') as fh: fdata = fh.read() geo_layer = GeoLayer(name=name, content=fdata, file_size=len(fdata)) s.add(geo_layer) finally: os.remove(tmp_fname) suc.append(u'KMLレイヤーを作成しました: %s' % name) kml_layer_name = '' layers = s.query(GeoLayer).order_by(GeoLayer.created.desc()) layers = [l.to_api_dict() for l in layers] except: s.rollback() raise else: s.commit() print 'commited' session['csrf'] = random_str(100) return self._render('kml.html.j2', err=err, suc=suc, layers=layers, kml_layer_name=kml_layer_name, session=self.beaker)