sethu
sethu

Reputation: 8421

Fest slows down while testing with swingx jxtreetable

I am not sure how to explain this. But I'll try.. Fest slows down to crawl while working with JXTreeTable of swingx. It doesn't slow down initially. It works fine for a while, but after a while when the same actions are repeated it slows down badly.

I have raised a bug for this in github. Please tell me if this is something that I am doing wrong instead. I am not able to reproduce the problem when I tried to create an SSCCE.

Anyway, here's a video of it slowing down.

http://screencast.com/t/liNttCw2In0w

At times 0.39s to 0.40 a set of operations are performed. These are done when there is one row in the JXTreeTable.

At time 0.49 to end of recording the same operation is repeated but there are now 3 rows in the table, it takes very long for the mouse to click.

I have attached a screenshot taken at the time when fest slows down, which attempts to explain it more

enter image description here

This is the code that does the work:

Step 1) Selecting a node from the tree is done as below:

JTreeFixture folioTreeFixture = importShareholders.panel("treePanel").tree("folioTree");

        folioTreeFixture.separator("~");
        folioTreeFixture.selectPath(new StringWrapper("Shareholders", true)+"~"+
             (ShareType.isEquity(shareType) ? new StringWrapper("Equity Folios", true) : new StringWrapper("Preference Folios", true))+"~"+
                new FolioTreeRep(folio.getName(),folioNo, shareType).toString());

Step 2) Searching and selecting a row from the JXTreeTable

int selectRow=-1;
JTableFixture table=importShareholders.table("historyTable");
for(int i=0;i<table.rowCount();i++){
    String certificateNumber = table.cell(TableCell.row(i).column(ShareholderHistoryTable.columnIndex(ShareholderHistoryTable.CERT_NO))).value();
    String remarks=table.cell(TableCell.row(i).column(ShareholderHistoryTable.columnIndex(ShareholderHistoryTable.REMARKS))).value();
    if(StringUtils.isEmpty(remarks) && StringUtils.isNotEmpty(certificateNumber) && Integer.parseInt(certificateNumber)==certNo){
        selectRow=i;
        break;
    }
}
if(selectRow==-1){
    fail("Couldn't find certificate number to transfer");
}

Step 3) Showing the pop up menu and clicking the row

table.showPopupMenuAt(TableCell.row(selectRow).column(0)).menuItem("btnTransfer").click();

I am not sure why its slowing down. Please let me know if there is any more info I can help with. Would be grateful for some help in solving the problem

I have profiled the application and I dont find anything untoward happening. I dont have a lot of experience profiling applications. I would be grateful if someone could have a second look at this. I profiled it with yourkit and have uploaded the snapshot dump here:

https://www.dropbox.com/s/dh976v01q9c3sgj/ImportShareholderData.shouldTransferAndSplit-2013-06-14-shutdown.snapshot.zip

Any help will be greatly appreciated..

EDIT:

I think I forgot to mention the same thing works when I do it manually. It only slows down with fest. That leads me to believe that there is an issue with fest maybe?

Sorry about that.

EDIT 2: As request by Marcin (sorry for the delay Marcin).. Here's the code when the first row is getting split

public List<Integer> splitRowEqually(ShareType shareType, String date, int folioNo, int certNo, int... certnos) throws NoSuchFieldException,     TorqueException {
    //select a tree node
    selectFolioInTree(shareType, folioNo);
    Pause.pause(new Condition("Wait until tab is created") {
        @Override
        public boolean test() {
            return importShareholders.tabbedPane().tabTitles().length>0;
        }
    });
    //select a row on the table to split
    int row=selectRowWithCertNunber(certNo);
    List<Integer> rowsIndexes=new ArrayList<Integer>();
    JTableFixture table = importShareholders.table();
    //show popup menu on that row and select split
    table.showPopupMenuAt(row(row).column(columnIndex(TRANS_TYPE))).menuItem("btnSplit").click();
    DialogFixture splitDialog=FinderUtilities.getDialogWithTitle("Split Share Certificate");
    splitDialog.textBox("tfDateOfSplit").setText(date);
    int noOfShares= Integer.parseInt(table.cell(row(row).column(columnIndex(NO_OF_SHARES))).value());
    int distFrom= Integer.parseInt(table.cell(row(row).column(columnIndex(DIST_NO_FROM))).value());
    int distTo= Integer.parseInt(table.cell(row(row).column(columnIndex(DIST_NO_TO))).value());
    //split the row into the number of times decided by the certnos array
    int noOfSharesInEachSplit=noOfShares/certnos.length;
    for(int i=0;i<certnos.length;i++){
        int distToInSplit = distFrom + noOfSharesInEachSplit-1;
        enterSplitRowDetails(splitDialog, certnos[i], distFrom, distToInSplit<=distTo ? distToInSplit : distTo);
        distFrom=distToInSplit+1;
        rowsIndexes.add(row++);
    }
    splitDialog.button("btnSplit").click();
    return rowsIndexes;
}

//selects a node from the left hand side tree
public void selectFolioInTree(final ShareType shareType,final int folioNo) throws TorqueException {
    JTreeFixture folioTreeFixture = importShareholders.panel("treePanel").tree("folioTree");
    folioTreeFixture.separator("~");
    // I use these wrapper classes - StringWrapper and FolioTreeRep, so that I can get a html 
    // string for the tree node like <html><b>Shareholder</b></html>
    String treePath = new StringWrapper("Shareholders", true) + "~" +
            (ShareType.isEquity(shareType) ? new StringWrapper("Equity Folios", true) : new StringWrapper("Preference Folios", true)) + "~" +
            new FolioTreeRep(mapOfFolioNames.get(folioNo), folioNo, shareType).toString();
    folioTreeFixture.clickPath(treePath);
}

//search the table for a row that contains the cert no provided in the Certificate Number column.
private int selectRowWithCertNunber(int certNo) throws NoSuchFieldException {
    int selectRow=-1;
    JTableFixture table=importShareholders.table("historyTable");
    for(int i=0;i<table.rowCount();i++){
        String certificateNumber = table.cell(row(i).column(columnIndex(CERT_NO))).value();
        String remarks=table.cell(row(i).column(columnIndex(REMARKS))).value();
        if(StringUtils.isEmpty(remarks) && StringUtils.isNotEmpty(certificateNumber) 
           && Integer.parseInt(certificateNumber)==certNo){
            selectRow=i;
            break;
        }
    }
    if(selectRow==-1){
        fail("Couldn't find certificate number to transfer");
    }
    return selectRow;
}

// enter details on the table in the SplitDialog
private void enterSplitRowDetails(DialogFixture splitDialog, int cert, int distFrom, int distTo) {
    splitDialog.button("btnAdd").click();
    int row = splitDialog.table().rowCount();
    splitDialog.table().enterValue(row(row - 1).column(0), String.valueOf(cert));
    splitDialog.table().enterValue(row(row - 1).column(1), String.valueOf(distFrom));
    splitDialog.table().enterValue(row(row - 1).column(2), String.valueOf(distTo));
}

Upvotes: 4

Views: 752

Answers (2)

user592704
user592704

Reputation: 3704

Emm... It is quite interesting question;

I suppose the question contains less really required details especially the robot integration and IO solutions details so I cannot just give you a proper answer...

Anyway, I'll try to analyze the problem in voice a little bit in my way...

First. According to your screenshot comments, I can notice that all "30s pauses or so" occur on some, as I can get it, stream reading process "select/search" (your app gets some data to output etc). So maybe it is much deeper than you think because it is probably thread problem;

I couldn't find the GuiQuery/GuiTask/GuiActionRunne classes usage in your code snippets so I may suggest the "synch problem" may take place in the mentioned case...

Second. OK... If it is still the thread problem I may suggest the robot and IO solutions are both in some ONE thread (the Main thread or something) because, according to your tips as "At times 0.39s to 0.40 a set of operations are performed. These are done when there is one row in the JXTreeTable." ... GUI is waiting for some process to be completed...

Third. And again... According to this issue as

"It is recommended to turn on an automated check to verify that all Swing components updates are done in Swing’s EDT (Event Dispatcher Thread). For those unfamiliar with the EDT, it is responsible for handling and updating all Swing widgets in a separate thread, causing that the application never loses responsiveness to user gestures (just in short, more about the EDT here). To do that, we add the following hook to the test:"

import org.fest.swing.edt.FailOnThreadViolationRepaintManager;
import org.junit.BeforeClass;
...
    @BeforeClass
    public static void setUpOnce() {
        FailOnThreadViolationRepaintManager.install();
    }

Next step is to launch the frame or dialog. As JUnit runs in its own thread, we must launch the frame or dialog through Fest, to ensure, again, that EDT is properly used:

import org.fest.swing.edt.GuiActionRunner;
import org.fest.swing.edt.GuiQuery;
import org.fest.swing.fixture.FrameFixture;
import org.junit.Before;
... 
    private FrameFixture testFrame; 
    private AllTypesFrame  frame;
... 
    @Before 
    public void setUp()  { 
        frame =  GuiActionRunner.execute(new GuiQuery<AllTypesFrame>() { 
            protected AllTypesFrame executeInEDT() { 
                return new AllTypesFrame(); 
            }
        }); 
        testFrame = new FrameFixture(frame); 
        testFrame.show(); 
    }

... makes me think it is maybe the "thread-problem" which is described in the First and Second tips...

so, as a conclusion, I can say that maybe you have to multi-thread your test a little more because it is obviously some kind of synch problem...

P.S. @sethu, before you start your debugging I want to point a little... I still suspect threads conflict is taking place here (see my previous tips) because, as I may notice, your code snippets are showing static expressions usage to invoke methods like Pause.pause(...) or FinderUtilities.getDialogWithTitle(...) etc I cannot see the whole project architecture so it is hard to analyze according the represented bits but it is pretty clear the "manual testing" goes fine because action listeners react in real time but fest testing does the annoying delays because it uses some "timer" to countdown until a click emulation occurs etc and of course it is a background process which needs a separate thread... Watch debugging carefully maybe somewhere in your code UI thread and fest thread do conflict (see static methods, thread.sleep etc) the points where fest thread could block (override) the UI's one... :S By the way what method Pause.pause(...) does?

P.P.S. If you have some additional information please comment my answer


Report if my answer helps you

Upvotes: 1

Marcin Sanecki
Marcin Sanecki

Reputation: 1334

I do not know what are your robot settings but you can at least try to set idleTimeout and other timeouts for the robot you use. The default timeout is 10 sec (look in org.fest.swing.core.Settings). After I decrease it (first 1000ms, next 100ms) I noticed that robot works faster.

robot().settings().idleTimeout(YOUR_TIMEOUT)

Here is my test setup and one test method. Hope is clear. Here you have my before/after

private static int testMethodCounter = 0;
private static EmergencyAbortListener mEmergencyAbortListener;
private FrameFixture workbenchFrame;
private Robot robot2;
private static final int myIdleTimeout = 100;

@Before
public void setUp() throws Exception {
    // my workaround to be able to start the app once and reuse for all tests 
    if (testMethodCounter == 0) {
        robot2 = BasicRobot.robotWithNewAwtHierarchy();
        GuiActionRunner.execute(new GuiTask() {
            @Override
            protected void executeInEDT() throws Throwable {
                 ApplicationLauncher.application(ProgramRun.class).start();
            }
        });
    } else {
        // the second test method see all before created gui components
        robot2 = BasicRobot.robotWithCurrentAwtHierarchy();
    }
    testMethodCounter++;
    robot2.settings().idleTimeout(myIdleTimeout);

    workbenchFrame = WindowFinder.findFrame(FrameNames.WORKBENCH.getName()).withTimeout(10000)
            .using(robot2);
}

@After
public void tearDown() {
    // current window will not be closed
    robot2.cleanUpWithoutDisposingWindows();

}

@Test
public void someSmokeTest() throws Exception {

    Pause.pause(1000);
    // perform some test specific gui actions

    // here is very important moment, I need new robot because 
    // workbenchFrame.button(ButtonNames.SOME_BUTTON_NAME).click(); creates new dialog
    // which will be avilable in AWT stack after creation

    robot2.cleanUpWithoutDisposingWindows();
    robot2 = BasicRobot.robotWithCurrentAwtHierarchy();
    // the new Robot needs timeout setup
    // without this I have long breaks between gui events
    robot2.settings().idleTimeout(myIdleTimeout);

    workbenchFrame.button(ButtonNames.SOME_BUTTON_NAME).click();

    DialogFixture dialog = WindowFinder.findDialog("dialog2") 
              .withTimeout(5000).using(robot2);

    // some actions on the dialog        


    // once again next new dialog
    workbenchFrame.menuItem(MenuItemNames.NAME).click();

    robot2.cleanUpWithoutDisposingWindows();
    robot2 = BasicRobot.robotWithCurrentAwtHierarchy();
    // and idleTimeout setup once again, new Robot needs new setup
    robot2.settings().idleTimeout(myIdleTimeout);

    // next actions + assertion
}

Upvotes: 0

Related Questions