Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix segfault in test #128

Closed
wants to merge 19 commits into from
Closed

Fix segfault in test #128

wants to merge 19 commits into from

Conversation

hanjinliu
Copy link
Owner

@hanjinliu hanjinliu commented Sep 24, 2023

Summary by CodeRabbit

  • Refactor: Improved resource management and cleanup in test setup and teardown, reducing the risk of memory leaks.
  • New Feature: Added methods to connect and disconnect item changed signals in table base class, enhancing modularity.
  • Refactor: Updated paste_data and _pass_pytable functions for better event handling.
  • Chore: Commented out configuration saving code in main widgets, indicating a potential change in configuration management.
  • Test: Updated test functions to use a new factory function for creating instances of TableViewer, improving test isolation.
  • Chore: Removed certain test functions, suggesting a shift in testing strategy or project scope.

@coderabbitai
Copy link

coderabbitai bot commented Sep 24, 2023

Walkthrough

This pull request primarily focuses on enhancing resource management and test setup in the codebase. It introduces garbage collection, event unlinking, and window closing to ensure proper cleanup after tests. Additionally, it refactors test functions to use a factory function for creating instances of TableViewer. Some functionalities and tests have been commented out or removed, indicating changes in requirements or testing approach.

Changes

File Summary
tests/conftest.py Introduced garbage collection, window closing, and object deletion for better resource management during tests.
tabulous/.../_table_base.py, tabulous/.../_mainwindow.py Added methods to connect and disconnect item changed signals. Refactored to unlink events from objects.
tabulous/.../_mainwidgets.py Commented out code related to configuration saving and confirmation before window closing.
tests/test_main_window.py, tests/test_keyboard_ops.py, tests/test_core.py, tests/test_magicwidget.py Refactored test functions to use a factory function for TableViewer creation. Commented out or removed some test functions.

🐇💻

Code refined, tests aligned,

In the realm of bits we dined.

Garbage collected, signals disconnected,

A cleaner codebase is now projected.

With every pull and every push,

We hop towards perfection, in a rush! 🚀🌟


Tips

Chat with CodeRabbit Bot (@coderabbitai)

  • Mention @coderabbitai in any review comment for bot assistance.
  • Note: Review comments are made on code diffs or files, not on the PR overview.

Pause Incremental Reviews

  • Insert @coderabbitai: ignore in the PR description to halt the bot's ongoing reviews. Remove the line to resume.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Status

Actionable comments generated: 2

Commits Files that changed from the base of the PR and between d76ca97 and 3940ad1.
Files selected for processing (1)
  • tests/conftest.py (2 hunks)
Additional comments (Suppressed): 1
tests/conftest.py (1)
  • 45-45: The clearing of QMainWindow._instances has been moved up before the event processing and garbage collection. This change seems logical as it ensures that the list of instances is cleared before any further cleanup actions are taken. However, please verify if there are any side effects of this change, especially if there are other parts of the code that rely on the state of QMainWindow._instances after the session fixture.

@pytest.fixture(scope="session", autouse=True)
def session():
from tabulous._utils import init_config, update_config, get_config
from tabulous._qt._mainwindow import QMainWindow
from qtpy.QtWidgets import QApplication
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import statement for QApplication is indented, which is not typical for Python. Imports are usually at the top of the file or function and not indented unless they are inside a conditional block. If this indentation is not intentional, it should be corrected.

-    from qtpy.QtWidgets import QApplication
+from qtpy.QtWidgets import QApplication

Comment on lines 44 to 47
QApplication.closeAllWindows()
QMainWindow._instances.clear()
for i in range(10):
QApplication.processEvents()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The addition of QApplication.closeAllWindows() and multiple calls to QApplication.processEvents() is a good practice for ensuring that all windows are closed and all events are processed before proceeding, which can help prevent segmentation faults during testing. However, the magic number 10 used in the loop could be replaced with a constant to improve readability and maintainability.

-    for i in range(10):
+    EVENT_PROCESSING_CYCLES = 10
+    for i in range(EVENT_PROCESSING_CYCLES):

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Status

Actionable comments generated: 3

Commits Files that changed from the base of the PR and between 3940ad1 and d04c1d7.
Files selected for processing (1)
  • tests/conftest.py (3 hunks)
Additional comments (Suppressed): 2
tests/conftest.py (2)
  • 47-47: The QApplication.closeAllWindows() method is being called after individual instances have been closed and deleted. This might be redundant if all instances have already been properly cleaned up. However, it can serve as a safety net in case some windows were missed during the individual cleanup process.

  • 53-54: The code checks if any instances of QMainWindow remain after cleanup and raises a RuntimeError if any are found. This is a good practice as it ensures that all resources have been properly cleaned up, helping to prevent memory leaks and other related issues.


gc.collect(1)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gc.collect(1) function is being called with an argument of 1. This means that only the youngest generation of objects will be collected. If there are older generations of objects that need to be collected, they will be ignored. Consider calling gc.collect() without any arguments to ensure all generations of objects are collected.

- gc.collect(1)
+ gc.collect()

Comment on lines 49 to 52
N_PROCESS_EVENTS = 10
for _ in range(N_PROCESS_EVENTS):
QApplication.processEvents()
gc.collect()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code is processing events multiple times (10 times in this case) to ensure thorough cleanup. While this may help in certain scenarios where events are queued up, it could potentially introduce unnecessary overhead if there are no events to process. Consider adding a condition to break the loop if no events are left to process.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Status

Actionable comments generated: 1

Commits Files that changed from the base of the PR and between d04c1d7 and 910e67d.
Files selected for processing (1)
  • tests/conftest.py (3 hunks)
Additional comments (Suppressed): 3
tests/conftest.py (3)
  • 3-3: The gc module is imported but it was already imported in the old code. Ensure that this import is necessary.

  • 47-51: Closing all application windows and processing events multiple times can be resource-intensive. Make sure that this is necessary and doesn't negatively impact performance. Also, consider making N_PROCESS_EVENTS a constant at the top of the file or a configurable parameter, rather than hardcoding it here.

  • 52-54: Garbage collection is forced and then a check is performed to see if any QMainWindow instances remain. If any do, a RuntimeError is raised. This is good for catching memory leaks, but make sure that raising an error is the desired behavior in this case, as it could halt the execution of your program.

@@ -16,13 +17,15 @@ def factory(show=False):

for viewer in viewers:
viewer.close()
viewer.native.deleteLater()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deleteLater() method is called on the native attribute of the viewer object. This assumes that every viewer has a native attribute. If there's a chance that some viewers might not have this attribute, this could raise an AttributeError. Consider adding a check to ensure that the native attribute exists before calling deleteLater().

- viewer.native.deleteLater()
+ if hasattr(viewer, 'native'):
+     viewer.native.deleteLater()

@coderabbitai
Copy link

coderabbitai bot commented Sep 24, 2023

Rate Limit Exceeded

You have exceeded the limit for the number of files or commits that can be reviewed per hour. Please wait 29 minutes and 59 seconds before requesting another review.

To re-trigger a review, convert this PR to a draft and then mark it as ready for review again. Alternatively, you can push a new commit to this PR.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Status

Actionable comments generated: 3

Commits Files that changed from the base of the PR and between 910e67d and 0b07ff9.
Files selected for processing (3)
  • tabulous/_qt/_table/_base/_table_base.py (2 hunks)
  • tabulous/widgets/_mainwindow.py (2 hunks)
  • tests/conftest.py (3 hunks)
Additional comments (Suppressed): 5
tests/conftest.py (4)
  • 3-3: The gc module is imported but not used in this hunk. However, it's used later in the code (line 52). It's a good practice to keep all imports at the top of the file.

  • 19-21: The viewer object now has additional cleanup steps: unlinking events and deleting the native object. This should help with memory management and prevent potential issues related to dangling references or memory leaks.

  • 47-51: Closing all application windows and processing events multiple times can help ensure that all resources are properly released and cleaned up. This is especially important in GUI applications where lingering resources can cause issues like memory leaks or crashes.

  • 52-54: Garbage collection is explicitly invoked to clean up any remaining objects. If there are still instances of QMainWindow, an exception is raised. This is a good practice as it ensures that all resources are properly cleaned up after each test session.

tabulous/widgets/_mainwindow.py (1)
  • 485-491: The close method has been updated to disconnect the ItemChangedSignal for each table in self.tables before clearing the list. This is a good practice as it ensures that signals are disconnected before the objects they're connected to are deleted, preventing potential issues such as dangling pointers or memory leaks. However, please verify if there are any other places in the code where tables are being removed without disconnecting their signals first.

Comment on lines +587 to +600
def _unlink_events(self):
_tablist = self.tables
_qtablist = self._qwidget._tablestack

_tablist.events.inserted.disconnect()
_tablist.events.removed.disconnect()
_tablist.events.moved.disconnect()
_tablist.events.renamed.disconnect()

_qtablist.itemMoved.disconnect()
_qtablist.tableRenamed.disconnect()
_qtablist.tableRemoved.disconnect()
_qtablist.tablePassed.disconnect()
_qtablist.itemDropped.disconnect()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _unlink_events method has been added to disconnect various events from tables and _qwidget._tablestack objects. This is a good practice for resource management and preventing potential issues such as memory leaks. However, it's important to ensure that this method is called whenever these objects are no longer needed or before they are deleted. Also, consider handling potential exceptions that might occur during disconnection of signals.

Comment on lines +233 to +237
def connectItemChangedSignal(self, *args, **kwargs) -> None:
pass

def disconnectItemChangedSignal(self):
pass
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The connectItemChangedSignal and disconnectItemChangedSignal methods are defined but do not perform any operations. If these methods are intended to be overridden in subclasses, consider making them abstract methods or adding a comment to clarify their purpose.

from abc import ABC, abstractmethod

class QMutableSimpleTable(ABC):
    # ...

    @abstractmethod
    def connectItemChangedSignal(self, *args, **kwargs) -> None:
        pass

    @abstractmethod
    def disconnectItemChangedSignal(self):
        pass

Comment on lines +1231 to +1236
def disconnectItemChangedSignal(self):
self.itemChangedSignal.disconnect()
self.rowChangedSignal.disconnect()
self.columnChangedSignal.disconnect()
self.evaluatedSignal.disconnect()
return None
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The disconnectItemChangedSignal method disconnects several signals without checking if they were connected in the first place. This could raise a TypeError if the signal was not connected. Consider adding error handling or checks to ensure that the signals are connected before attempting to disconnect them.

def disconnectItemChangedSignal(self):
    for signal in [self.itemChangedSignal, self.rowChangedSignal, self.columnChangedSignal, self.evaluatedSignal]:
        try:
            signal.disconnect()
        except TypeError:
            pass
    return None

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Status

Actionable comments generated: 0

Commits Files that changed from the base of the PR and between 0b07ff9 and f1457d0.
Files selected for processing (1)
  • tabulous/_qt/_mainwindow/_mainwidgets.py (1 hunks)
Files skipped from review due to trivial changes (1)
  • tabulous/_qt/_mainwindow/_mainwidgets.py

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Status

Actionable comments generated: 0

Commits Files that changed from the base of the PR and between f1457d0 and e1bdffd.
Files selected for processing (1)
  • tabulous/_qt/_mainwindow/_mainwidgets.py (1 hunks)
Files skipped from review due to trivial changes (1)
  • tabulous/_qt/_mainwindow/_mainwidgets.py

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Status

Actionable comments generated: 0

Commits Files that changed from the base of the PR and between e1bdffd and b351dff.
Files selected for processing (1)
  • tests/test_main_window.py (1 hunks)
Files skipped from review due to trivial changes (1)
  • tests/test_main_window.py

@coderabbitai
Copy link

coderabbitai bot commented Sep 24, 2023

Rate Limit Exceeded

You have exceeded the limit for the number of files or commits that can be reviewed per hour. Please wait 29 minutes and 59 seconds before requesting another review.

To re-trigger a review, convert this PR to a draft and then mark it as ready for review again. Alternatively, you can push a new commit to this PR.

@coderabbitai
Copy link

coderabbitai bot commented Sep 24, 2023

Rate Limit Exceeded

You have exceeded the limit for the number of files or commits that can be reviewed per hour. Please wait 25 minutes and 43 seconds before requesting another review.

To re-trigger a review, convert this PR to a draft and then mark it as ready for review again. Alternatively, you can push a new commit to this PR.

@coderabbitai
Copy link

coderabbitai bot commented Sep 24, 2023

Rate Limit Exceeded

You have exceeded the limit for the number of files or commits that can be reviewed per hour. Please wait 21 minutes and 16 seconds before requesting another review.

To re-trigger a review, convert this PR to a draft and then mark it as ready for review again. Alternatively, you can push a new commit to this PR.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Status

Actionable comments generated: 2

Commits Files that changed from the base of the PR and between b351dff and d74d313.
Files selected for processing (2)
  • tests/test_keyboard_ops.py (3 hunks)
  • tests/test_main_window.py (7 hunks)
Additional comments (Suppressed): 6
tests/test_keyboard_ops.py (3)
  • 10-13: The change from directly creating a TableViewer instance to using the make_tabulous_viewer factory function is a good practice. It enhances modularity and maintainability of the test suite by centralizing the creation of TableViewer instances. However, ensure that the make_tabulous_viewer function is correctly implemented and returns a valid TableViewer instance.

  • 21-24: Similar to the previous comment, the use of make_tabulous_viewer here is a good practice. Ensure that this function is correctly implemented and returns a valid TableViewer instance.

  • 43-46: Again, the use of make_tabulous_viewer is a good practice. Please verify that this function is correctly implemented and returns a valid TableViewer instance.

tests/test_main_window.py (3)
  • 11-16: The make_tabulous_viewer factory function is used to create instances of TableViewer. This change simplifies the test setup and makes it more maintainable. However, ensure that this factory function correctly initializes the TableViewer instances as required by these tests.

  • 26-32: The pytest.mark.parametrize decorator for viewer_cls has been commented out. This means that the test is no longer being run for both TableViewer and TableViewerWidget. If this was intentional, make sure that the functionality is still adequately tested. If it was not intentional, consider uncommenting these lines.

  • 111-116: Similar to the previous comment, the pytest.mark.parametrize decorator for viewer_cls has been commented out in multiple places. Verify whether this change was intentional and if the functionality is still adequately tested.

@@ -21,12 +21,12 @@ def test_add_layers(viewer_cls: type[TableViewerWidget]):
assert viewer.tables[1].name == "Data-0"
assert np.all(df == viewer.tables[0].data)
assert np.all(agg == viewer.tables[1].data)
viewer.close()
# viewer.close()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The viewer.close() method call has been commented out. If the viewer is not closed after each test, it might lead to resource leaks or interference between tests. Consider uncommenting these lines or ensuring proper cleanup in another way.

@@ -121,4 +123,4 @@ def test_bind_keycombo(viewer_cls: type[TableViewerWidget]):
viewer.keymap.register("T")(mock)
viewer.keymap.register("T", overwrite=True)(print)

viewer.close()
# viewer.close()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the previous comment, the viewer.close() method call has been commented out in multiple places. Ensure that resources are properly cleaned up after each test to prevent potential issues.

@coderabbitai
Copy link

coderabbitai bot commented Sep 24, 2023

Rate Limit Exceeded

You have exceeded the limit for the number of files or commits that can be reviewed per hour. Please wait 4 minutes and 27 seconds before requesting another review.

To re-trigger a review, convert this PR to a draft and then mark it as ready for review again. Alternatively, you can push a new commit to this PR.

@coderabbitai
Copy link

coderabbitai bot commented Sep 24, 2023

Rate Limit Exceeded

You have exceeded the limit for the number of files or commits that can be reviewed per hour. Please wait 15 minutes and 2 seconds before requesting another review.

To re-trigger a review, convert this PR to a draft and then mark it as ready for review again. Alternatively, you can push a new commit to this PR.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Status

Actionable comments generated: 0

Commits Files that changed from the base of the PR and between d74d313 and 31652d4.
Files selected for processing (3)
  • tests/test_core.py (1 hunks)
  • tests/test_keyboard_ops.py (3 hunks)
  • tests/test_magicwidget.py (1 hunks)
Files skipped from review due to trivial changes (2)
  • tests/test_core.py
  • tests/test_magicwidget.py
Additional comments (Suppressed): 3
tests/test_keyboard_ops.py (3)
  • 8-12: The make_tabulous_viewer factory function is used instead of directly creating an instance of TableViewer. This change could be due to the need for additional setup or teardown steps that are encapsulated in the factory function. Ensure that this new approach does not affect the functionality being tested.

  • 19-25: The make_tabulous_viewer factory function is used here as well. As mentioned earlier, verify that this change doesn't impact the test's functionality.

  • 41-45: Again, the make_tabulous_viewer factory function is used. Please ensure that this change doesn't affect the test's functionality.

@hanjinliu hanjinliu closed this Sep 24, 2023
@hanjinliu hanjinliu deleted the fix-segfault branch September 24, 2023 15:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant