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

I39: Add Execute All in judge button to bypass stop on first failure #918

Merged
merged 9 commits into from
Feb 13, 2024

Conversation

SamanwaySadhu
Copy link
Collaborator

@SamanwaySadhu SamanwaySadhu commented Jan 28, 2024

Description of what the PR does

Add Execute All button on TestResultsPane. It can bypass stop on first failure condition and run the code on all testcases if a judge desires.

Issue which the PR addresses

Fixes #39

Environment in which the PR was developed (OS, IDE, Java version, etc.)

Windows 10, Eclipse 2021-12 R, JDK 8u381 (1.8), C++ 13.2.0

Precise steps for testing the PR (i.e., how to demonstrate that it works correctly)

  • Run the Ant script build.xml in the pc2v9 project.
  • Start a PC2 server, loading a sample contest such as "mini".
  • Start a PC2 Admin.
  • In Problems pane make sure one of the problems have judging type set to Manual, if not edit and set to Manual. Also check the box for stop-on-first-failure.
  • In Auto Judge pane make sure one of the judges have Problems (none selected), if not edit and set.
  • Start the contest.
  • Start a PC2 team.
  • Make a submission for a problem which had manual judging type set. Make sure it fails on the first testcase.
  • Starte a PC2 judge. Make sure to use the one which no problems set for auto-judge.
  • Select the submission from New Runs pane and request run.
  • In Select Judgement for Run {:id} window click on Execute Run.
  • It will fail on first test case and the rest are not executed.
  • Now click on Execute All button in Test Results Pane. It should now execute all test cases despite failures.

image

image

@johnbrvc johnbrvc added this to the 9.10.0 milestone Jan 28, 2024
@johnbrvc johnbrvc requested review from johnbrvc and troy2914 January 28, 2024 14:17
Copy link
Collaborator

@johnbrvc johnbrvc left a comment

Choose a reason for hiding this comment

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

See individual comments on each file for notes.

@johnbrvc
Copy link
Collaborator

One general question I have before I actually start testing the PR is: I noticed you duplicated all the "executeRun" code in the TestResultsPane from SelectJudgementPaneNew.executeRun(). Is there any reason you couldn't leverage the code already in SelectJudgementPaneNew directly instead of duplicating it? The problem with duplicating it is if a change is required in executeRun, we have to make that change in 2 places now. I believe that RestResultsFrame/TestResultsPane is modeless, so maybe some sort of interface could be used to call executeRun from SelectJudgementPaneNew? I haven't thought about it much, what are your thoughts on this?

@clevengr
Copy link
Contributor

clevengr commented Feb 9, 2024

The "Steps for Testing the PR" shows a sample GUI screenshot, which is apparently supposed to be related somehow to testing the PR. However, as it shows in the GUI header, there is only ONE total test case for the problem. Using a problem with only one test case doesn't really help in explaining how to test the ability to execute multiple test cases after the first one fails...

* refreshes the resultspanebuttonpanel
* includes/removes execute all button based on stop-on-first-failure
*/
private void updateResultsPaneButtonPanel() {
Copy link
Contributor

Choose a reason for hiding this comment

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

This method essentially duplicates the contents of method getResultsPaneButtonPanel() (the singleton accessor/constructor for the button panel), then conditionally adds the ExecuteAll button. The problem with doing it this way is that if in the future a change needs to be made to the layout of the button panel (say, another button needs to be added), that change now needs to be made in TWO places. This makes the code very brittle and subject to errors when future changes are made. The code from the accessor/constructor should not be duplicated; rather, the original accessor/constructor should be updated to call a separate method which conditionally adds (or removes) the ExecuteAll button. That separate method can then be called separately when it is necessary to update the button panel, without the need to reconstruct the entire panel separately. (It will probably take some minor rearrangement of how the methods work and when they are called, but doing this will greatly improve the maintainability of the code.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I kind of figured out a way to fix this however one small issue that arises is that the Execute All button now comes up in the end after the Close button. So far, I'm unable to figure out how to change the ordering of the buttons. I'm still looking into it. Basically, the issue is that there is a JPanel in FlowLayout with a bunch of components, how to now add some new components in between some existing components.

image

Copy link
Contributor

Choose a reason for hiding this comment

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

One solution, admittedly not necessarily ideal, would be to create two sub-panels in the "Button Panel": one to hold the "permanent" buttons (the checkbox, plus the Select All, Unselect All, Compare Selected, and Execute All buttons), and a second panel to hold (just) the Close button. Then you could add/remove the Execute All button from the "left sub-panel" and the "right sub-panel" containing (just) the Close button would always appear to the right of whatever was in the "left sub-panel". This however would certainly add some additional (perhaps undesirable) complexity to the GUI. Another solution might be to use a GridBagLayout for the button panel -- but if you've never fooled with those, fair warning: they are pretty complicated (but, very powerful for controlling layouts).

A better solution of course would be if there's a way to manage the insertion point of a component in a JPanel with a flow layout -- but I'm not sure that's possible without rebuilding the entire panel (which as I pointed out isn't really desirable -- at least, there shouldn't be two different places where it's done...)

If none of the above helps, my own feeling is that it's better to have proper code organization, at the expense of having the Execute All button show up on the far left (others may feel differently...) Overall, I would investigate the "two sub-panels" idea first; it might be relatively clean and easy to do.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated with the "two sub-panels" approach

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm a little late to the party, but I have been doing some testing on this branch.
Instead of a left/right panel, why not just always add the execute all button, but just set its visiblity:
getExecuteAllButton().setVisible(isExecuteAllButtonVisible) ? Basically the button will always "be there", just not visible for the cases we don't want to see it. I suppose you could do the same for the spacing strut too. I quickly tried this and it seems to work. Basically, as a test, I just added:

            //add space
            getResultsPaneButtonPanelLeft().add(getExecuteAllHorizontalStrut());
            
            // add a control button to execute all test cases
            getResultsPaneButtonPanelLeft().add(getExecuteAllButton());
            
            getExecuteAllButton().setVisible(false);

to your "getResultsPaneButtonPanelLeft()" method. And changed updateResultsPaneButtonPanelLeft() to:

    private void updateResultsPaneButtonPanelLeft() {

        if (currentProblem != null && currentProblem.isStopOnFirstFailedTestCase()) {
            isExecuteAllButtonVisible = true;
        }
        else {

            isExecuteAllButtonVisible = false;
        }
        getExecuteAllButton().setVisible(isExecuteAllButtonVisible);
    }

I think this would work as it was before you added the left and right panels. Or, did you try this and find it didn't work? It seems cleaner, assuming of course it works without the left/right panels.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated as per suggestion. Earlier I had tried this approach, must have done something wrong as I was seeing a large space when the Execute All button set to be invisible.

Comment on lines 1531 to 1541
// Add action listener whether Test results pane button is clicked
testResultsFrame.addActionListenerToExecuteAll(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new Thread(new Runnable() {
public void run() {
executeRun(true);
}
}).start();
}
});
Copy link
Contributor

Choose a reason for hiding this comment

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

This block of code (adding an actionListener() to the ExecuteAll button) is a bit awkward; normally, code like this belongs in the singleton accessor/constructor (in this case, in TestResultsPane.getExecuteAllButton(), right after the executeAllButton = new JButton() statement). As presently coded, this block makes calls through a series of methods in different classes (TestResultsFrame.addActionListenerToExecuteAll() --> TestResultsPane.addActionListenerToExecuteAll() --> TestResultsPane.getExecuteAllButton()). If changes are made later to methods in that sequence, it can result in an error in the calling class (SelectJudgementPaneNew). In other words, there is a lot of tight coupling between these classes, which in general is not good.

I do recognize that moving this block of code into the singleton accessor/constructor would raise the problem of how the ActionListener code is going to be able to execute the statement executeRun(true); since the execute() method is in the SelectJudgementPaneNew class, not in the TestResultsPane class where the ExecuteAll button is defined. I suspect that moving the code to the ExecuteAll constructor would require adding code to allow it to reference back to the SelectJudgementPaneNew.execute() method, so I'm not necessarily requesting a change. It just seems awkward to have this long sequence of addActionListenerToExecuteAll() method calls.

Copy link
Collaborator Author

@SamanwaySadhu SamanwaySadhu Feb 11, 2024

Choose a reason for hiding this comment

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

One workaround for it can be that TestResultsPane object should have an object of SelectJudgementPaneNew as a member variable. When an object of TestResultsPane is created inside SelectJudgementPaneNew we can set the TestResultsPane 's member with this (referring to the SelectJudgementPaneNew calling the function) and use the executeRun function from there. I'm going to explore this approach and might require making the executeRun function public.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, this is one approach to solve the problem -- instead of having the parent (composing) class (SelectJudgementPaneNew) have references downward to the composed classes (like TestResultsFrame and TestResultsPane), support the ability of the composed class (TestResultsPane) to have a reference upward to its composing class. This would be slightly cleaner, although it still represents tight coupling between the classes.

In any case, if you take that approach I don't think method SelectJudgementPaneNew.executeRun() should be public; it should be protected -- which will allow classes in the same package to access its methods while prohibiting general public access. This will work since both SelectJudgementPaneNew and TestResultsPane are in the same package (edu.csus.ecs.pc2.ui).

@@ -2319,6 +2394,7 @@ public void setData(Run run, RunFiles runFiles, Problem problem, ProblemDataFile
this.currentRunFiles = runFiles;
this.currentProblem = problem;
this.currentProblemDataFiles = problemDataFiles;
updateResultsPaneButtonPanel();
Copy link
Contributor

Choose a reason for hiding this comment

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

As noted above, the contents of this method (updateResultsPaneButtonPanel()) should not be reconstructing the entire button panel from scratch. Rather, the method should simply be updating what is (potentially) changing : it should adjust the ExecuteAll button, not reconstruct the entire panel.

Copy link
Contributor

@clevengr clevengr left a comment

Choose a reason for hiding this comment

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

I have built the code and done a run-time test; it appears to work as intended (and is a nice improvement).

I have made a number of comments in the code about things I'd "recommend" being fixed/updated. Most of them are minor and I could live with them as-is. However, there is one change which I think needs to be made prior to approval: see the comment in class TestResultsPane regarding the inappropriate duplication of code in method updateResultsPaneButtonPanel(). That method should not be recreating the entire button panel from scratch (for the reasons I listed in the code-review comment). Rather, it should only be "updating" the button panel by adding/removing the "Execute All" button, as appropriate.

@SamanwaySadhu
Copy link
Collaborator Author

The "Steps for Testing the PR" shows a sample GUI screenshot, which is apparently supposed to be related somehow to testing the PR. However, as it shows in the GUI header, there is only ONE total test case for the problem. Using a problem with only one test case doesn't really help in explaining how to test the ability to execute multiple test cases after the first one fails...

Updated the screenshots

Comment on lines +1531 to +1541
// Add action listener whether Test results pane button is clicked
testResultsFrame.addActionListenerToExecuteAllButton(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new Thread(new Runnable() {
public void run() {
executeRun(true);
}
}).start();
}
});
Copy link
Contributor

@clevengr clevengr Feb 11, 2024

Choose a reason for hiding this comment

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

I still do not understand why the code to add the actionListener() to the Execute All button is here, in the SelectJudgementPaneNew class. The Execute All button is created as an object when method TestResultsPane.getExecuteAllButton() is invoked. That method is invoked from only three places: methods addActionListenerToExecuteAllButton(), enableExecuteAllButton(), and updateResultsPaneButtonPaneLeft().

The FIRST time one of those methods calls getExecuteAllButton(), the button will be created. The button does not go away (cease to exist) just because method updateResultsPaneButtonPanelLeft() chooses to add it to, or remove it from, the "left panel" -- it remains as a valid object in memory, with its current state intact (including any actionListeners attached to it. If method updateResultsPaneButtonPanelLeft() removes it from the panel, and then subsequently adds it back, it is the exact same button (object) which is being "added back". Therefore, there's no reason to continually "update" the actionListener attached to the button.

The singleton accessor/constructor for the button (method getExecuteAllButton()) should add the actionListener to the button once, when the JButton is constructed. This avoids all the unnecessary code which is constantly updating the button's actionListener. (It does of course require implementing the facility for the TestResultsPane to have an upward access (reference) to the SelectJudgementPaneNew which contains the executeRun() method which the actionListener needs to call. But that seems a small price to pay for eliminating all the "action listener setting", and for being able to keep the entire construction of the JButton in a single place.)

Copy link
Contributor

@clevengr clevengr left a comment

Choose a reason for hiding this comment

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

I have reviewed the updated code and also done a run-time test. The updates fixed the main issue I had requested a change on, and the new code works fine.

I made one comment about the (IMHO) unnecessary updating of the actionListener for the Execute All button; I think it makes more sense to add that actionListener when the button is first constructed (in TestResultsPane.getExecuteAllButton()). See my comments in the "conversation" for details.

I'm prepared to approve this PR if you will either fix the issue with the actionListener, or provide a description of why the changes required to fix it (that is, the code to give the TestResultsPane access to the parent SelectJudgementPaneNew object so the actionListener can be added in the singleton constructor/accessor method) are more complicated than what would be gained by removing the complicated update action listener methods.

Copy link
Contributor

@clevengr clevengr left a comment

Choose a reason for hiding this comment

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

On further consideration, I am changing my mind regarding my previous review comments. While I don't like the fact that the actionListener for the Execute All button is being set from a composing class (specifically, SelectJudgementPaneNew) rather than being set in the singleton accessor/constructor for the button, there are other considerations.

Specifically, changing it so the ActionListener is set in the constructor method would require adding the same type of code to TestResultsPane that already exists in SelectJudgementPaneNew and TestResultsPane -- code to access UP the hierarchy of the composition (instead of DOWN the hierarchy as it presently does). So making the change is probably a net-zero gain.

In addition, this hierarchy is a classic example of a composition. In general, the design rules for a composition include that the parent controls the lifetime of its children. This can be extended to say that the parent "knows about" its children, but that the children in general do not know about their parents. While there are valid reasons for making exceptions to this rule, there doesn't seem to be one here; and as coded, the parents (SelectJudgementPaneNew and TestResultsFrame) do know about the "child" (TestResultsPane and its subcomponents). So I think the current structure is fine (I wish I could come up with a better one, but I can't...)

So I'm adding an Approval to this PR.

Copy link
Collaborator

@johnbrvc johnbrvc left a comment

Choose a reason for hiding this comment

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

Looks good and seems to work as advertised. The "how to test" instructions were a bit misleading since they imply there will be more than one problem to select from when turning off computer judging. When I loaded 'mini' there was exactly one problem, sumit. No big deal, just pointing it out.

I would like a to see a response to the comment I made about left/right panels and the setVisible() business I suggested before I approve this.

Copy link
Collaborator

@johnbrvc johnbrvc left a comment

Choose a reason for hiding this comment

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

Looks good. Works as I would expect it to.

Copy link
Contributor

@clevengr clevengr left a comment

Choose a reason for hiding this comment

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

I reviewed the updated code, then built and ran the system. Works great. @johnbrvc 's solution (toggling visibility instead of using two sub-panels) was a better suggestion than mine; I'm glad you went ahead and implemented it.
Marking PR as "Approved".

@johnbrvc johnbrvc merged commit 9a44d05 into pc2ccs:develop Feb 13, 2024
3 checks passed
@SamanwaySadhu SamanwaySadhu deleted the I39_add_judge_executeall branch February 13, 2024 21:05
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.

On Judge UI add feature to execute all test cases if stop-on-first-failed-test-case is true
3 participants