------------------------------------------------------------- README Notes written 2024 (AssertJ Swing GLI GUI testing beginnings were in 2018-2019) ------------------------------------------------------------- Sections I Code Layout II Background Reading III Compiling and Running IV GLI CODE CHANGES - EXPLANATION ------------------- CODE LAYOUT ------------------- - ext/testing/src/src/org/greenstone/gsdl3/testing/GSGUITestingUtil.java (AssertJ Swing, GUI testing helper functions) - ext/testing/src/src/org/greenstone/gsdl3/testing/GSTestingUtil.java (Selenium, browser testing helper functions) - ext/testing/src/src/gstests/tutorials/RunGLITest.java (GLI portion of the tutorial tests as well as running command line scripts with the help of Greenstone-wide SafeProcess.java class: both standard Greenstone scripts can get run, as well as custom scripts that represent the command line/background tasks we carry out for some tutorials.) - ext/testing/src/src/gstests/tutorials/BrowserTest.java (Greenstone Reader Interface/browser portion of the tutorial tests) - ext/testing/src/src/org/greenstone/gsdl3/testing/GSBasicTestingUtil.java (Background file copy/file system helper functions) When you issue the command "ant run-tutorials-tests", it starts off the tutorial tests. Testing starts in RunGLITest.java, - public void tutorial_tests() is now the sole assertj swing unit test, as marked up @Test annotation. - It calls some standard setup (and later cleanup) functions that would get called for each test. - tutorial_tests() consists of a series of functions, let's call them tutorial functions. Each tutorial function goes through a tutorial, though some tutorials internally use helper functions. - These tutorial functions do GLI automated testing (using helper functions in GSGUITestingUtil.java), but when it's time for previewing (invoked by functions createBuildPreviewCycle and quickFormatPreviewCycle), BrowserTest.java's functions get called: - BrowsertTest generally contains a function for each tutorial (which make use of helper functions in GSTestingUtil.java): each preview operation within a tutorial gets a switch case statement in that tutorial's BrowserTest function, and all browser tests pertaining to that preview are covered in that case statement. In any tutorial, each call to createBuildPreviewCycle or quickFormatPreviewCycle from RunGLITest passes the preview operation number to the BrowserTest function for that tutorial. You can have n previews per tutorial, checking various things look as they should in the Greenstone Reader Interface (browser) at the given stage of the tutorial. And so the parameter "nthPreview" passed to most tutorial functions in BrowserTest.java indicates which preview of that tutorial is being tested (using switch case statements on the nthPreview variable, so hopefully the code will read straightforwardly). ASSERTJ SWING AND SELENIUM VERSIONS: - Selenium (selenium-server jar, previously selenium-server-standalone jar): We started with selenium-server-standalone-3.9.1.jar and currently have started using selenium-server-4.0.0-alpha-5.jar, as it allows us to minimise the selenium driven browser. But this newer jar is still to be tested against all tutorials. Possibly may shift straight to using selenium-server-4.0.0-alpha-7.jar as code appears to compile and run with it too. For why we don't at present use newer versions of selenium, see TODO file. - Assertj-swing (assertj-core jar, assertj-swing jar, assertj-swing-junit jar): We started with assertj-core-3.8.0.jar, assertj-swing-3.8.0.jar, assertj-swing-junit-3.8.0.jar It seems like we can tentatively try moving to newer versions 3.17.1 as long as we're sure its jars are being used for compiling and running, and not old ones. See details in file TODO. Selenium Web Drivers for browsers: ---------------------------------- GeckoDriver (linux) and GeckoDriver.exe (windows) for Firefox For Safari on Mac: - https://nightwatchjs.org/guide/browser-drivers/safaridriver.html - https://developer.apple.com/library/archive/releasenotes/General/WhatsNewInSafari/Articles/Safari_10_0.html#//apple_ref/doc/uid/TP40014305-CH11-SW39 "WebDriver Support Safari on macOS supports WebDriver, which lets you automate web-content testing. It provides a set of interfaces to manipulate DOM elements and control the browser’s behavior. You can enable Remote Automation in the Develop menu and then launch the server using /usr/bin/safaridriver. For information about library integrations as they become available, see the information about Selenium WebDriver." ------------------- BACKGROUND READING ------------------- See the comments section at the top of ext/testing/src/src/gstests/tutorials/RunGLITest.java It lists the URLs and reading that I went through pertaining to junits, assertj swing and (if anything) on selenium to start off the automated gli testing work back in 2018. I can further gather the following from the current code. The following may be useful websites - interspersed throughout our code that uses assert swing, I find I'd added references to this website: https://joel-costigliola.github.io/assertj/assertj-swing-basics.html Good page for finding dialogs/windows, even if they may appear at some point in time: https://joel-costigliola.github.io/assertj/assertj-swing-lookup.html - https://junit.org/junit4/ (we appear to use junit4) - there's also the older wiki page by Sam that has his own work (not yet incorporated): https://wiki.greenstone.org/doku.php?id=internal:gs3_automated_testing Pages that are useful - https://joel-costigliola.github.io/assertj/swing/api/org/assertj/swing/exception/EdtViolationException.html - VERY USEFUL BASICS: https://docs.oracle.com/javase/6/docs/api/javax/swing/package-summary.html#threading - EDT and AssertJ Swing: https://joel-costigliola.github.io/assertj/assertj-swing-edt.html - Possibly useful: https://assertj-swing.readthedocs.io/en/latest/assertj-swing-basics/#base-test-class - Java GUI testing tools discussion: https://sqa.stackexchange.com/questions/18554/open-source-tools-for-automation-of-java-gui-application-testing - AssertJ core: https://joel-costigliola.github.io/assertj/assertj-core-quick-start.html and https://joel-costigliola.github.io/assertj/assertj-core-conditions.html NOTES on moving any code into Runnable class inside Gatherer.invokeOnDispatchThread (which does SwingUtilities.invokeAndWait/invokeLater): - Only an outer class' member variables and final local variables are available to any inner class. This detail is necessary to know when calling SwingUtilities.invokeAndWait/invokeLater(new Runnable() { public void run() { // here, only the outer class' member and final local vars are available // As a closure is created with those variables, like in JavaScript } }); - Rewrite any 'this' reference as '.this' when moving code containing any THIS ptr references into inner Runnable class ---------------------- COMPILING AND RUNNING ---------------------- 0. Have a fresh GS3, preferrably a code checkout from SVN so that you can tweak the code on any errors. Then set up the environment: source ./gs3-setup.sh 1. cd into GSDL3SRCHOME/ext and checkout the testing extension: svn co https://trac.greenstone.org/browser/gs3-extensions/testing/trunk/ testing 1a. Copy the template file ext\testing\src\lib's GLItest.properties.in as GLItest.properties and edit the value for the line samplefiles.path=/TYPE/YOUR/PATH/TO/sample_files/ Make sure you've checked out and unzipped sample_files.zip there where you specify it. Alternatively, check out sample_files from svn into some location: svn co https://svn.greenstone.org/documentation/trunk/tutorial_sample_files sample_files On Windows, the following style works (if you have sample_files unzipped or checked out at the location specified): samplefiles.path=C:/Users/me/sample_files/ 1b. Ensure gli is compiled and jarred up. cd gli ./makegli.sh && ./makejar.sh cd .. 1c. For now, need the server started up separately from GLI. I'm not sure why, but this certainly has helped while implementing the tutorial tests so far, so that I don't need to wait for the server to stop and start each time, and can quickly abort (step 4). ./ant-logreset-restart-with-exts.sh 2. Run the default target in its src folder, which is build-tutorials-jar (which builds both the helper functions and the sequence of testing helper functions that get run) GS3/ext/testing/src>ant 3. Compile and run cycle: ant build-all-jars * ant run-tutorials-test (See point 5 first for EventDispatchThread/EDT exceptions encountered so far in GLI and fixes made) *build-all-jars calls ant targets: - build-util-jar: to compile up the testing helper function in GSGUITestingUtil.java (and in future hopefully also GSTestingUtil.java that will have helper functions using Selenium to test out web browser behaviour) - compile-tutorials-tests: to compile and jar up RunTestGLI.java, the sequence of steps that make up a test (like the sequnce of actions to complete the test of a GS3 tutorial). 4. To terminate the auto GLI gui testing: Ctrl+Shift+A when the GLI window is in focus. You may further then may still need to press Ctrl-C in the terminal to return the prompt. Ctrl-C works to kill the browser automatically opened by the testing code using Selenium for browser testing. NOTES: * Before GLI appears, you can kill the testing with Ctrl-C. This will also terminate GLI if it suddenly appears. * When GLI runs automatically, it's very modal and there's no stopping it with random key presses. To stop GLI running, RunTestGLI.java documents we need to press Ctrl+Shift+A. Until the testing is done or an exception terminates the automated testing prematurely, testing won't otherwise stop. * There are occasions when Ctrl+Shift+A has no effect, usually when it's waiting to press one of the menu options or on rare cases of unexpected popups that appear that are not part of the streamlined testing as set out, and then the whole testing stops. On such an occasion, after 10 or 20 seconds the testing suite will stop by itself as it times out waiting to do the next expected step. 5. To launch gems in testing mode to display assigned names to widgets: gli>./gems.sh -testing_mode Unfortunately, for GEMS the assigned names occasionally were still overridden/renamed by later widgets getting names assigned. Exceptions thrown during GEMS GUI testing by assertj swing will indicate the actual names available. -------------------------------- GLI CODE CHANGES - EXPLANATION -------------------------------- The major GLI changes, to do with invoking code on the Event Dispatch Thread (EDT), were committed with revision 39192: https://trac.greenstone.org/changeset/39192 and https://trac.greenstone.org/changeset/39195 Further changes: GLI preview set to not open the browser when in testing mode, where the testing code opens a Selenium-driver enabled web browser: https://trac.greenstone.org/changeset/39197 and https://trac.greenstone.org/changeset/39221 They've been made to be as unobtrusive as possible, by correcting existing code from running on non-EDT threads to running on the EDT where required. As these changes could in theory still impact GLI functioning, they have been made reversible with a global on/off switch, as well as locally reversible at the level of each function call to invokeInEDT(). As these changes involve passing in a Runnable to invokeInEDT, there are anonymous inner classes being used. The use of outer variables in inner classes mandates that the variables be final or class members. Classes like collect/CollectionManager.java and file/FileManager.java's NewFolderOrDummyTask and RenameTask inner classes have had to be modified by turning local variables into member variables and then nulling them when no longer in use, as well as adding methods to access "result" variables when they've finished executing. --- The class gli/src/org/greenstone/gatherer/gui/TestingPreparation.java has - static global flags called - TEST_MODE, if we ran GLI in test mode as the automated testing does, and - DEBUGGING_TEST_MODE which is set to true and, if in TEST_MODE, will print all names set on GLI java Components so far in an automated GLI session. Every time a custom popup or dialog or new tab opens, more names may get set by GLI and these will get displayed. - static methods like variants of setNamesRecursively(Component a_root_component) to set all names of the root component and its subcomponent widgets. For testing with assertj swing to access specific widgets, Java GUI Components (JComponents) need to be assigned unique names, like JavaScript allows element to have unique IDs. Although GLI calls this static function early on upon its own JFrame to set names for all visible and other instantiated widgets when GLI first runs, not all of GLI's widgets are created on GLI startup. Some only get created upon certain user events. - You will therefore want to call this method from a GLI widget class (usually a dialog/popup or panel of controls) that isn't created on GLI startup but gets created upon some sequence of user events in GLI. - You need to do so before setVisible(true) is called on that JDialog/GLI widget class, as the names need to be assigned before the dialog appears, so that assertj swing functions have access to them when our testing code needs them. The testing code will then be able to access expected dialogs, popups and other widgets, and their subcontrols, by component name. import org.greenstone.gatherer.gui.TestingPreparation; //this refers to the root widget instance to be named along with subcontrols TestingPreparation.setNamesRecursively(this); // Call TestingPreparation.setNamesRecursively(rootWidget) before setVisible(true)/show(). // setVisible(true); - Beware: assertj swing doesn't like duplicate names, for the same reason you're not supposed to have duplicate element IDs in JavaScript. See https://stackoverflow.com/questions/50702401/choose-one-of-three-identical-swing-components-using-assertj The default TestingPreparation.setNamesRecursively(Widget) function sets component names uniquely on GLIButtons, as well as any other gli/src/org/greenstone/gatherer/gui/ root class, ensure their subcompontents are correctly named. These might get names like ., e.g. LoadCollectionPrompt.collection_name_textfield. Widgets outside the GLI gui package would get assigned very generic names, like "JViewport.JList" or "JPanel.JTextField". This is not a problem and is in fact fine as long as they're unique for their container, be it the dialog they appear in or the visible part of the GLI frame ("window" in assertj swing terminology). As I wasn't aware when I started the GLI automated testing work that gli/src/org/greenstone/gatherer/cdm contained classes such as *Manager.java with widget inner classes (that either implement Control or where the widget inner class names end on -Control), the names used for cdm widget classes since early on the implementation of the actual tutorial tests' utility functions were very generic as above. This was fine for the longest time as they tended to be unique for their container, especially as buttons were either GLIButtons and had unique names, or were accessed by label. But eventually, I occasionally hit on two duplicate named items in a container, e.g. two of "JPanel.JTextField". We usually do NOT want to call TestingPreparation.setNamesRecursively() on the Control widget class, as our testing code is interspersed with references to generic "JViewport.JList" and more, as many subsections of the Design Pane and Format Pane tend to have similarly organised controls with a JList, move up and down buttons, etc. And it was handy to make the testing code take advantage of this by generically referring to the different subsections of such JPanels. So methods like configurePluginOrClassifier() would work without knowing the exact name of the Plugin panel class or Classifier panel class. However, some panels have extra controls and these controls may be of the same type as others, in which case we have duplicate names. So we need to rename some but not all controls of the container class. In such cases, locate the Control class in GLI's cdm package, then before the widget root class is made visible, either: - call setNamesRecursivelyForControl(Component root) if all the controls in this widget (inner) class are not already referenced by the testing code - UNTESTED: call setIndividualSubcomponentNames(Component root, Component ... subcomponents) and pass in the widget root class and just the subcontrol member variables that need to be named. They'll be named .. --- For correcting EDT (Event Dispatch Thread) issues --- You're supposed to do GUI stuff on the Event Dispatch Thread (EDT) in Java. However, GLI doesn't always do so. And the assertj swing tests will throw an exception and stop all testing when it catches GLI running GUI stuff on a different thread from the EDT. This can be a pain as the testing can't proceed until GLI is fixed up, even though GLI -- if run by itself -- was not visibly broken (GLI was still functional even if we occasionally saw "repaint" related errors where the stacktrace never mentions any greenstone GLI packages). Generally, the solution would be to call SwingUtilities.invokeLater() for ASYNC operations or SwingUtilities.invokeAndWait() for SYNC operations. Of course, sometimes the calling code is already on the EDT, so we should take that into account as well. All of the above and more are taken care of in Gatherer's new static methods that start with Gatherer.invokeInEDT_...(), to be described in detail later. But now there is the dilemma of "fixing" GLI (for automated GLI testing) when GLI itself is not palpably broken and thereby introducing bugs into GLI. The user experiences GLI, not GLI's automated testing. So GLI should always be functional. Yet, we'd like GLI for automated testing to ideally use the same GLI code (especially for threading) as GLI itself does, so that we're actually testing GLI as the user experiences it and not some obscure branches of GLI code that are unique to when GLI is running in testing mode. 1. However, in case making EDT related changes to GLI for testing to work has created unforeseen problems, we want GLI developers to be able to turn off all EDT changes with a global switch. This will allow the many EDT changes to be committed in the main GLI trunk svn branch instead of creating a separate GLI branch and later having to merge it (or worse: all the hard work for GLI automated testing going stale). For this, we have the following global flag in the GLI code: Gatherer.TURN_OFF_EVENTDISPATCH_CHANGES 2. Alternatively, if some *specific* EDT related change(s) (specific calls to our custom invokeInEDT() functions) cause problems, we want to be able to switch just those off. This is useful if EDT related changes were made early on and suddenly we discover that such and such a change makes GLI behave differently: we can run GLI in a mode where we BYPASS those particular EDT change(s) and compare against running GLI with those change(s) in place to help us track down where to fix them. It also buys time: we can have GLI work as it always did for the user by passing in the BYPASS_WHEN_NOT_TESTING flag to such an EDT change that had made GLI less optimal than before the change, whereas, while in testing mode GLI will run with such an EDT change in place as is crucial for testing (because assertj swing will throw an exception and abort on any EDT violation). For this, we have the parameter BYPASS_WHEN_NOT_TESTING that we can pass into the EDT methods described below. Let us return to the Gatherer's methods for transferring GUI code onto the EDT. There are 2 variants, and they are named for the original GLI code -- **prior to EDT changes** -- that they are **replacing**. The name being indicative of the before state of affairs is useful, as it tells you how GLI used to operate before I made my code changes. So that, should we need to use the global flag TURN_OFF_EVENTDISPATCH_CHANGES, or switch off transfer to EDT changes on a per change (per invokeInEDT_...() method call) basis with BYPASS_WHEN_NOT_TESTING, we know what sort of behaviour GLI is falling back to in each case. Or how to easily put the code back to before I made my massive EDT related changes to GLI. The 2 specific variants of invokeInEDT_...() are as follows: - Gatherer.invokeInEDT_replacesProceedInCurrThread( "<.optional inner classname>.() - optional string to uniquely identify location of caller", Gatherer., //Gatherer., // overloaded to take this flag, explained further below Runnable runnable // some (sequence of) GUI/event code that should happen on EDT }); - Gatherer.invokeInEDT_replacesThreadStart( "<.optional inner classname>.() - optional string to uniquely identify location of caller", Gatherer., //Gatherer., // optional flag Runnable aThread // pass in the Thread that, prior to EDT changes, GLI used to create and launch with pattern: (new SomeThread()).start() }); GLI has overloaded versions of both variants. The commonly used versions of both the above variants of invokeInEDT only take 3 parameters: the caller description string, SYNC/ASYNC, Runnable object. SYNC/ASYNC words were chosen to be reminiscent of JavaScript meanings. - SYNC, does invokeAndWait(): wait for the function to return (so a blocking call) before you can proceed doing work in your thread. - ASYNC, does invokeLater(): call the function (which will run on the EDT thread when it can), and continue doing your work in the current thread. If you're unsure from the context of which of SYNC or ASYNC to pass in when you're needing to call invokeInEDT(): although we want to avoid blocking calls, Java (unlike JavaScript) is largely coded synchronously, the familiar linear execution of code. So go with SYNC and test. If it blocks, switch to ASYNC. The invokeInEDT_replacesThreadStart() variant is rarely to be used and requires the following conditions to all be satisified: - *Prior to EDT changes*, GLI should have created and launched a thread (that creates/displays a dialog or does other graphical things in its run() code, as explained below) with the coding pattern: (new SomeThread()).start(); - The final parameter should be a Thread. A Thread is also a Runnable, but has special methods and when they're called, see the difference between Runnable and Thread in Java's API, especially for Therad.start() versus Thread/Runnable.run(). - The Thread should be doing some graphical stuff in its run method, like creating a dialog. Obviously, we don't want JDialogs created on a thread other than the EDT as that would be an EDT violation. However, GLI developers created this separate worker Thread specifically so that the GLI graphics don't freeze while waiting for some processing to happen. So the solution is for the dialog to be created and displayed on the EDT thread, any further code that does GUI stuff in that GUI class or its inner classes that cause EDT violations will also need to be moved onto the EDT thread. But any non-GUI processing that the GLI Thread class does should happen on the actual non-EDT thread. And it would do non-GUI processing, else there's no reason for GLI developers to have run it on its own Thread, but see next point below. - Check that the GUI class that the Thread creates in its run() method should have worker inner classes that do non-GUI things. If there is no such thing, and it's all GUI stuff (like the creation and display of a dialog and returning values, and no more), then there's no need for a separate Thread to run the GUI class, and it should be rewritten to just launch the GUI class on the EDT, instead of creating a thread to launch the GUI class in run(). This is just a sanity check, it hasn't been applicable so far: so far, "(new SomeThread()).start();" where SomeThread.run() launches a GLI GUI component class where the GUI component class has its own inner Thread classes that do deserve to be run their separate non-EDT threads. See the calls to new ExplodeMetadataDatabasePromptTask() or new ReplaceSrcDocWithHtmlPromptTask() in FileManager.java. The classes ExplodeMetadataDatabasePromptTask and ReplaceSrcDocWithHtmlPromptTask are graphical, but they have inner Thread classes that do non-graphical processing: ExplodeMetadataDatabasePromptTask contains inner class ExplodeMetadataDatabaseTask extends Thread and ReplaceSrcDocWithHtmlPromptTask contains inner class ReplaceSrcDocWithHtmlTask extends Thread GLI, prior to the EDT changes, launched these two graphical Task classes in a new (non-EDT) thread with new ExplodeMetadataDatabasePromptTask(file).start(); and new ReplaceSrcDocWithHtmlPromptTask(files).start(); These have been replaced with Gatherer.invokeInEDT_replacesThreadStart("FileManager.explodeMetadataDatabase Task", Gatherer.ASYNC, new ExplodeMetadataDatabasePromptTask(file)); and Gatherer.invokeInEDT_replacesThreadStart("FileManager.replaceSrcDocWithHTML task", Gatherer.ASYNC, new ReplaceSrcDocWithHtmlPromptTask(files)); to force the creation of the new ExplodeMetadataDatabasePromptTask/ReplaceSrcDocWithHtmlPromptTask objects to now happen on the EDT, as they're graphical classes. We don't need to worry about teh non-GUI heavy lifting they'd have done, for which the GLI developers had chosen to run them in their own threads, as that's already done in a separate thread by their inner Thread classes ExplodeMetadataDatabaseTask and ReplaceSrcDocWithHtmlTask. In contrast, the usual variant you'll want to call whenever you are trying to fix EDT violations, is Gatherer.invokeInEDT_replacesProceedInCurrThread(). This method takes whatever block of existing GLI code (that does GUI stuff) which you embed into the Runnable that used to run in the current thread (whichever that was) and force it to run on the EDT thread, if it wasn't already doing so. What this all reads as is as follows: - if you need to fix an EDT violation of regular code that happens in the current thread (whichever that is), replace that regular code with the appropriately named invokeInEDT_replacesProceedInCurrThread() variant. - if the line of code you wanted to replace looks like "(new aThread()).start();" where the instantiated thread object calls a GUI class constructor in its run() method, then ensure the GUI class already has a thread inner class that does the non-GUI processing. If so, replace "(new aThread(...)).start();" pattern with: "Gatherer.invokeInEDT_replacesThreadStart(, , new aThread(...));" Ideally, we'd like to cut out the extra Thread middleman altogether as such a GUI class ought to just have been run on the EDT, instead of a separate non-GUI thread. However, as we want to be able to put GLI back to its original state before EDT changes were made, we need to keep the existing way of doing things intact. This way the global On/Off switch to TURN_OFF_EVENTDISPATCH_CHANGES, and the local on/off switch to turn individual invokeOnEDT calss On/Off, will put GLI back to working as before. -------------------------- ACTUAL GLI CODE CHANGES (incomplete) -------------------------- The following just captures a few of the early diffs, some of which may have since been replaced by improved chanegs. And there have been a great many more changes made since locally, to be captured here if not, ideally, committed to SVN (for which I need permission to commit the local GLI code changes to the main branch). Running GLI used to immediately fail with Event Dispatch Thread exceptions (EDT exceptions). To get past this basic issue, a. - either in GathererProg.java I needed to move the instantiation of Gatherer into the SwingUtilities.invokeLater()'s run() at the bottom of GathererProg.main(), before gatherer.openGUI() - or I instantiate g_man in Gatherer inside a SwingUtilities.invokeLater() run() method b. In Gatherer.java, there were issues with the fileManager instantiation. So I put the contiguous lines instantiating assoc_man, f_man, c_man, recycle_bin into an invokeLater too. c. Then I can run "ant run-tutorials-test" and it jogs along OK automatically launching and interacting with GLI until it hits the snag I remember from long ago: the ProgressBar holding us up. These changes are documented in the svn diffs below. d. Many more such changes to GLI have now been made that I haven't here documented and would like to commit to svn with Dr Bainbridge's permission, after he looks them over (or a sample of them). More such changes may continue to be necessary as our testing expands to try more and more branches of GLI's code and EDT exceptions get triggered and get fixed. I'd prefer to commit all EDT related changes to GLI all in one go, at least on a "per tutorial" basis (per working test sequence of steps/actions that exercise a tutorial). Reminder: to shift code into the Event Dispatch Thread/EDT, there's SwingUtilities.invokeLater(), which is asynchronous. To be synchronous (code that Must happen now because the code that follows is dependent on results/successful finish of the sequence of events being shifted into the EDT), use SwingUtilities.invokeAndWait() in a try/catch. e. With Dr Bainbridge's permission, all GLI changes got committed in the end, since there's a switch (boolean flag in Gatherer.java) to turn off the changes for invoking on the EDT.