Table of Contents
Test Report
View CI Run 2117 | Commit 5efb2e6
🧪 Test Report
Generated on 2025-08-12 13:33:13 CEST
🧾 General Info
- duration: 4.866118431091309
- root: /workspace/tligui_y/slic
- environment: {}
📋 Summary
- Failed: 3
- Passed: 3
- Total: 6
- Collected: 6
🔎 Tests
❌ Failed (3)
-
📄 test_utils_elog.py
↳ Function: test_post_local
-
❌ Test 1
📌 Setup phase
duration:
0.0003806529566645622outcome:
passed📌 Call phase
duration:
0.008349655196070671outcome:
failedcrash:
path: /workspace/tligui_y/slic/.pixi/envs/default/lib/python3.8/site-packages/elog/logbook.py lineno: 315 message: elog.logbook_exceptions.LogbookInvalidMessageID: Invalid message ID: None returnedtraceback:
- path: tests/test_utils_elog.py lineno: 45 message: None - path: .pixi/envs/default/lib/python3.8/site-packages/elog/logbook.py lineno: 315 message: LogbookInvalidMessageIDlongrepr:
def test_post_local(): logbook = elog.open( hostname="http://localhost", port=8080, user="robot", password="testpassword", use_ssl=False, logbook="demo" ) attributes = { "Author": "robot", "Subject": "Test simple", "Category": "General", } message = "Hello from local test" > msg_id = logbook.post(message, attributes=attributes, encoding="HTML") tests/test_utils_elog.py:45: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = <elog.logbook.Logbook object at 0x7fe57dfb99a0> message = 'Hello from local test', msg_id = None, reply = False attributes = {'Author': 'robot', 'Category': 'General', 'Encoding': 'HTML', 'Subject': 'Test simple', ...} attachments = [], suppress_email_notification = False, encoding = 'HTML' timeout = None, kwargs = {} new_attachment_list = [('Text', ('', b'Hello from local test'))] objects_to_close = [] attributes_to_edit = {'Author': b'robot', 'Category': b'General', 'Encoding': b'HTML', 'Subject': b'Test simple', ...} def post(self, message, msg_id=None, reply=False, attributes=None, attachments=None, suppress_email_notification=False, encoding=None, timeout=None, **kwargs): """ Posts message to the logbook. If msg_id is not specified new message will be created, otherwise existing message will be edited, or a reply (if reply=True) to it will be created. This method returns the msg_id of the newly created message. :param message: string with message text :param msg_id: ID number of message to edit or reply. If not specified new message is created. :param reply: If 'True' reply to existing message is created instead of editing it :param attributes: Dictionary of attributes. Following attributes are used internally by the elog and will be ignored: Text, Date, Encoding, Reply to, In reply to, Locked by, Attachment :param attachments: list of: - file like objects which read() will return bytes (if file_like_object.name is not defined, default name "attachment<i>" will be used. - paths to the files All items will be appended as attachment to the elog entry. In case of unknown attachment an exception LogbookInvalidAttachment will be raised. :param suppress_email_notification: If set to True or 1, E-Mail notification will be suppressed, defaults to False. :param encoding: Defines encoding of the message. Can be: 'plain' -> plain text, 'html'->html-text, 'ELCode' --> elog formatting syntax :param timeout: Define the timeout to be used by the post request. Its value is directly passed to the requests post. Use None to disable the request timeout. :param kwargs: Anything in the kwargs will be interpreted as attribute. e.g.: logbook.post('Test text', Author='Rok Vintar), "Author" will be sent as an attribute. If named same as one of the attributes defined in "attributes", kwargs will have priority. :return: msg_id """ attributes = attributes or {} attributes = {**attributes, **kwargs} # kwargs as attributes with higher priority attachments = attachments or [] if encoding is not None: if encoding not in ['plain', 'HTML', 'ELCode']: raise LogbookMessageRejected('Invalid message encoding. Valid options: plain, HTML, ELCode.') attributes['Encoding'] = encoding if suppress_email_notification: attributes["suppress"] = 1 # THE ATTACHMENT STRATEGY WHEN DEALING WITH POST MODIFICATION # # 1. Does the message on the server have already attachments? # 1.1 - We read the message getting the existing attachment list. # 1.2 - Add to the attributes dictionary one line for each attachment like this: # attributes['attachmentN'] = timestamped_filename_name # # 2. Do we have new attachments? # 2.1 - Those are in the new_attachment_list. This is a list of this type: # [ ('attfileN', ('filename', fileobject)) ] # 2.2 - We need to loop over all the new attachments: # 2.2.1 - Does a file already on the server with the same name exist? # 2.2.1.1 - No: OK. Then we go ahead with the next attachment. # 2.2.1.2 - Yes: # 2.2.1.2.1 - Are the two files identical? # 2.2.1.2.1.1 - Yes: then we remove this current entry from the new_attachment_list and we leave the one # already on server. # 2.2.1.2.1.2 - No: # 2.2.1.2.1.2.1 - Then the file has been update. # 2.2.1.2.1.2.2 - We need to remove the file on server first (using special post) # 2.2.1.2.1.2.3 - We have to remove the old attachment from the attributes dictionary. # if attachments: # here we accomplish point 2.1. # new_attachment_list is something like [ ('attfileN', ('filename', fileobject)) ] new_attachment_list, objects_to_close = self._prepare_attachments(attachments) else: objects_to_close = list() new_attachment_list = list() attributes_to_edit = dict() if msg_id: # Message exists, we can continue if reply: # Verify that there is a message on the server, otherwise do not reply to it! self._check_if_message_on_server(msg_id) # raises exception in case of none existing message attributes['reply_to'] = str(msg_id) else: # Edit existing attributes['edit_id'] = str(msg_id) attributes['skiplock'] = '1' # here we accomplish point 1.1. # existing_attachments_list is something like: # [ 'https://elog.url.com/logbook/timestamped_filename' ] msg_to_edit, attributes_to_edit, existing_attachments_list = self.read(msg_id) for attribute, data in attributes.items(): new_data = attributes.get(attribute) if new_data is not None: attributes_to_edit[attribute] = new_data i = 0 existing_attachments_filename_list = list() for attachment in existing_attachments_list: # here we accomplish point 1.2. We strip the timestamped_filename from the whole URL. attributes_to_edit[f'attachment{i}'] = os.path.basename(attachment) existing_attachments_filename_list.append(os.path.basename(attachment)[14:]) i += 1 # let's accomplish 2.2. Loop over all new attachment duplicate_attachment_list = list() for new_attachment in new_attachment_list: # the new_attachment_list is something like: # [ ('attfileN', ('filename', fileobject)) ] new_attachment_filename = new_attachment[1][0] if new_attachment_filename in existing_attachments_filename_list: # a file with the same name existing already on the server. # we need to check if the two files are the same. # read the content of the new file new_attachment_content = new_attachment[1][1].read() # don't forget to reset the fileobj to the beginning of the file new_attachment[1][1].seek(0) # get the existing attachment content attachment_index = existing_attachments_filename_list.index(new_attachment_filename) existing_attachment_content = self.download_attachment( url=existing_attachments_list[attachment_index], timeout=timeout ) # check if the two contents are the same if new_attachment_content == existing_attachment_content: # yes. then we don't upload a second copy. we remove the current entry from the list duplicate_attachment_list.append(new_attachment) else: # no. they are not the same file. we will replace the existing file with the new one # first: we need to remove the attachment from the server using the dedicated method self.delete_attachment(msg_id, attributes=attributes_to_edit, attachment_id=attachment_index, timeout=timeout, text=msg_to_edit) # now we can remove this attachment from the auxiliary lists. existing_attachments_filename_list.pop(attachment_index) existing_attachments_list.pop(attachment_index) # now we need to rebuild the attributes dictionary for the part concerning the attachments. # we remove all of them first keys_to_be_removed = list() for key in attributes_to_edit.keys(): if key.startswith('attachment'): keys_to_be_removed.append(key) if key.startswith('delatt'): keys_to_be_removed.append(key) for key in keys_to_be_removed: del attributes_to_edit[key] # now we rebuild it for i, attachment in enumerate(existing_attachments_list): attributes_to_edit[f'attachment{i}'] = os.path.basename(attachment) # remove all duplicate attachments from the new_attachment_list for attach in duplicate_attachment_list: new_attachment_list.remove(attach) else: # As we create a new message, specify creation time if not already specified in attributes if 'When' not in attributes: attributes['When'] = int(datetime.now().timestamp()) if not attributes_to_edit: attributes_to_edit = attributes # Remove any attributes that should not be sent _remove_reserved_attributes(attributes_to_edit) # Make requests module think that Text is a "file". This is the only way to force requests to send data as # multipart/form-data even if there are no attachments. Elog understands only multipart/form-data new_attachment_list.append(('Text', ('', message.encode('iso-8859-1')))) # Base attributes are common to all messages self._add_base_msg_attributes(attributes_to_edit) # Keys in attributes cannot have certain characters like whitespaces or dashes for the http request attributes_to_edit = _replace_special_characters_in_attribute_keys(attributes_to_edit) # All string values in the attributes must be encoded in latin1 attributes_to_edit = _encode_values(attributes_to_edit) try: response = requests.post(self._url, data=attributes_to_edit, files=new_attachment_list, allow_redirects=False, verify=False, timeout=timeout) # Validate response. Any problems will raise an Exception. resp_message, resp_headers, resp_msg_id = _validate_response(response) # Close file like objects that were opened by the elog (if path for file_like_object in objects_to_close: if hasattr(file_like_object, 'close'): file_like_object.close() except requests.Timeout as e: # Catch here a timeout o the post request. # Raise the logbook excetion and let the user handle it raise LogbookServerTimeout('{0} method cannot be completed because of a network timeout:\n' + '{1}'.format(sys._getframe().f_code.co_name, e)) except requests.RequestException as e: # Check if message on server. self._check_if_message_on_server(msg_id) # raises exceptions if no message or no response from server # If here: message is on server but cannot be downloaded (should never happen) raise LogbookServerProblem('Cannot access logbook server to post a message, ' + 'because of:\n' + '{0}'.format(e)) # Any error before here should raise an exception, but check again for nay case. if not resp_msg_id or resp_msg_id < 1: > raise LogbookInvalidMessageID('Invalid message ID: ' + str(resp_msg_id) + ' returned') E elog.logbook_exceptions.LogbookInvalidMessageID: Invalid message ID: None returned .pixi/envs/default/lib/python3.8/site-packages/elog/logbook.py:315: LogbookInvalidMessageID📌 Teardown phase
duration:
0.000344966072589159outcome:
passed
↳ Function: test_post
-
❌ Test 5
📌 Setup phase
duration:
0.0001245136372745037outcome:
passed📌 Call phase
duration:
0.003189053852111101outcome:
failedcrash:
path: /workspace/tligui_y/slic/slic/utils/elog.py lineno: 17 message: AttributeError: 'Elog' object has no attribute 'post_elog'traceback:
- path: tests/test_utils_elog.py lineno: 122 message: None - path: slic/utils/elog.py lineno: 17 message: AttributeErrorlongrepr:
def test_post(): elog = get_test_elog() title = "AUTHOR_OVERRIDE_TEST" text = "This is a message" author = "robot" > elog.post(text, attributes = {"Author": author}) tests/test_utils_elog.py:122: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = <slic.utils.elog.Elog object at 0x7fe57de53d30> args = ('This is a message',) kwargs = {'Author': 'robot', 'attributes': {'Author': 'robot'}} def post(self, *args, **kwargs): kwargs.setdefault("Author", self.user) #return self._log.post(*args, **kwargs) > return self.post_elog(*args, **kwargs) E AttributeError: 'Elog' object has no attribute 'post_elog' slic/utils/elog.py:17: AttributeError📌 Teardown phase
duration:
0.00014035077765583992outcome:
passed
↳ Function: test_screenshot
-
❌ Test 6
📌 Setup phase
duration:
0.00011890428140759468outcome:
passed📌 Call phase
duration:
0.004473010078072548outcome:
failedcrash:
path: /workspace/tligui_y/slic/slic/utils/elog.py lineno: 17 message: AttributeError: 'Elog' object has no attribute 'post_elog'traceback:
- path: tests/test_utils_elog.py lineno: 143 message: None - path: slic/utils/elog.py lineno: 22 message: in screenshot - path: slic/utils/elog.py lineno: 17 message: AttributeErrorlongrepr:
mock_screenshot_class = <MagicMock name='Screenshot' id='140623636257232'> @patch("slic.utils.elog.Screenshot") def test_screenshot(mock_screenshot_class): with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmp: fake_path = tmp.name tmp.write(b"fake image data") mock_instance = mock_screenshot_class.return_value mock_instance.shoot.return_value = [fake_path] elog = get_test_elog() test_msg = "SCREENSHOT_INTEGRATION_TEST_MSG_456" > entry_id = elog.screenshot(message=test_msg) tests/test_utils_elog.py:143: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ slic/utils/elog.py:22: in screenshot self.post(message, **kwargs) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = <slic.utils.elog.Elog object at 0x7fe57df07fd0> args = ('SCREENSHOT_INTEGRATION_TEST_MSG_456',) kwargs = {'Author': 'robot', 'attachments': ['/tmp/tmpb3vwmz32.png']} def post(self, *args, **kwargs): kwargs.setdefault("Author", self.user) #return self._log.post(*args, **kwargs) > return self.post_elog(*args, **kwargs) E AttributeError: 'Elog' object has no attribute 'post_elog' slic/utils/elog.py:17: AttributeError📌 Teardown phase
duration:
0.000147990882396698outcome:
passed
-
✅ Passed (3)
-
📄 test_utils_elog.py
↳ Function: test_get_default_elog_instance_with_direct_password_and_real_check
-
✅ Test 2
📌 Setup phase
duration:
0.00015159696340560913outcome:
passed📌 Call phase
duration:
0.009392366278916597outcome:
passed📌 Teardown phase
duration:
0.0001456988975405693outcome:
passed
↳ Function: test_get_default_elog_instance_asks_password_and_opens
-
✅ Test 3
📌 Setup phase
duration:
0.00013034790754318237outcome:
passed📌 Call phase
duration:
0.009084322955459356outcome:
passed📌 Teardown phase
duration:
0.00013644574210047722outcome:
passed
↳ Function: test_get_default_elog_with_path_home
-
✅ Test 4
📌 Setup phase
duration:
0.00011953618377447128outcome:
passed📌 Call phase
duration:
0.014088630676269531outcome:
passed📌 Teardown phase
duration:
0.0001273290254175663outcome:
passed
-
📚 Collected files
✅ (1 tests)
-
✅
- Outcome:
passed - result:
- nodeid: tests/test_utils_elog.py type: Module - Outcome:
✅ tests (1 tests)
-
✅ tests/test_utils_elog.py
- Outcome:
passed - result:
- nodeid: tests/test_utils_elog.py::test_post_local type: Function lineno: 29 - nodeid: tests/test_utils_elog.py::test_get_default_elog_instance_with_direct_password_and_real_check type: Function lineno: 52 - nodeid: tests/test_utils_elog.py::test_get_default_elog_instance_asks_password_and_opens type: Function lineno: 65 - nodeid: tests/test_utils_elog.py::test_get_default_elog_with_path_home type: Function lineno: 83 - nodeid: tests/test_utils_elog.py::test_post type: Function lineno: 114 - nodeid: tests/test_utils_elog.py::test_screenshot type: Function lineno: 130 - Outcome:
⚠️ Warnings
Warnings nº1
message: invalid escape sequence \-
category: DeprecationWarning
when: collect
filename: /workspace/tligui_y/slic/.pixi/envs/default/lib/python3.8/site-packages/bsread/h5.py
lineno: 207
Warnings nº2
message: The module numpy.dual is deprecated. Instead of using dual, use the functions directly from numpy or scipy.
category: DeprecationWarning
when: collect
filename: /workspace/tligui_y/slic/.pixi/envs/default/lib/python3.8/site-packages/scipy/fft/__init__.py
lineno: 97