From da43209aa67da9b490d5057516f5595fc581aa70 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 5 May 2020 21:47:55 +0200 Subject: [PATCH 01/10] CommandLineTest: Run findBuildPaths only once Previously, it was ran before each test, but just running it once before all tests is sufficient. So switch from @Before to @BeforeClass and make the its result variable static. --- app/test/processing/app/CommandLineTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/test/processing/app/CommandLineTest.java b/app/test/processing/app/CommandLineTest.java index 9ac760b1fbb..70e8c8fd85a 100644 --- a/app/test/processing/app/CommandLineTest.java +++ b/app/test/processing/app/CommandLineTest.java @@ -35,7 +35,7 @@ import org.apache.commons.compress.utils.IOUtils; import org.fest.assertions.Assertions; -import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import processing.app.helpers.OSUtils; @@ -43,11 +43,11 @@ public class CommandLineTest { - File buildPath; - File arduinoPath; + private static File buildPath; + private static File arduinoPath; - @Before - public void findBuildPaths() throws Exception { + @BeforeClass + public static void findBuildPaths() throws Exception { buildPath = new File(System.getProperty("user.dir")); while (!new File(buildPath, "build").isDirectory()) { buildPath = buildPath.getParentFile(); From 9d777fb6c1b497c12d4211f9c679164a6cbea40d Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 5 May 2020 21:49:32 +0200 Subject: [PATCH 02/10] CommandLineTest: Add runArduino helper This abstracts some the common code (running Arduino, copying output, waiting for completion, checking result) from all testcases into a single method. This simplifies each testcase, but also prepares for adding more common arguments to all runs in a subsequent commit. --- app/test/processing/app/CommandLineTest.java | 68 +++++++++----------- 1 file changed, 31 insertions(+), 37 deletions(-) diff --git a/app/test/processing/app/CommandLineTest.java b/app/test/processing/app/CommandLineTest.java index 70e8c8fd85a..0a7ea064ba3 100644 --- a/app/test/processing/app/CommandLineTest.java +++ b/app/test/processing/app/CommandLineTest.java @@ -32,6 +32,10 @@ import static org.junit.Assert.*; import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import java.util.Arrays; import org.apache.commons.compress.utils.IOUtils; import org.fest.assertions.Assertions; @@ -72,58 +76,57 @@ public static void findBuildPaths() throws Exception { System.out.println("found arduino: " + arduinoPath); } + public Process runArduino(boolean output, boolean success, File wd, String[] extraArgs) throws IOException, InterruptedException { + Runtime rt = Runtime.getRuntime(); + + List args = new ArrayList(); + args.add(arduinoPath.getAbsolutePath()); + args.addAll(Arrays.asList(extraArgs)); + + Process pr = rt.exec(args.toArray(new String[0]), null, wd); + if (output) { + IOUtils.copy(pr.getInputStream(), System.out); + IOUtils.copy(pr.getErrorStream(), System.out); + } + pr.waitFor(); + if (success) + assertEquals(0, pr.exitValue()); + return pr; + } + @Test public void testCommandLineBuildWithRelativePath() throws Exception { - Runtime rt = Runtime.getRuntime(); File wd = new File(buildPath, "build/shared/examples/01.Basics/Blink/"); - Process pr = rt - .exec(arduinoPath + " --board arduino:avr:uno --verify Blink.ino", null, - wd); - IOUtils.copy(pr.getInputStream(), System.out); - pr.waitFor(); - assertEquals(0, pr.exitValue()); + runArduino(true, true, wd, new String[] { + "--board", "arduino:avr:uno", + "--verify", "Blink.ino", + }); } @Test public void testCommandLinePreferencesSave() throws Exception { - Runtime rt = Runtime.getRuntime(); File prefFile = File.createTempFile("test_pref", ".txt"); prefFile.deleteOnExit(); - Process pr = rt.exec(new String[] { - arduinoPath.getAbsolutePath(), + runArduino(true, true, null, new String[] { "--save-prefs", "--preferences-file", prefFile.getAbsolutePath(), "--get-pref", // avoids starting the GUI }); - IOUtils.copy(pr.getInputStream(), System.out); - IOUtils.copy(pr.getErrorStream(), System.out); - pr.waitFor(); - assertEquals(0, pr.exitValue()); - pr = rt.exec(new String[] { - arduinoPath.getAbsolutePath(), + runArduino(true, true, null, new String[] { "--pref", "test_pref=xxx", "--preferences-file", prefFile.getAbsolutePath(), }); - IOUtils.copy(pr.getInputStream(), System.out); - IOUtils.copy(pr.getErrorStream(), System.out); - pr.waitFor(); - assertEquals(0, pr.exitValue()); PreferencesMap prefs = new PreferencesMap(prefFile); assertNull("preference should not be saved", prefs.get("test_pref")); - pr = rt.exec(new String[] { - arduinoPath.getAbsolutePath(), + runArduino(true, true, null, new String[] { "--pref", "test_pref=xxx", "--preferences-file", prefFile.getAbsolutePath(), "--save-prefs", }); - IOUtils.copy(pr.getInputStream(), System.out); - IOUtils.copy(pr.getErrorStream(), System.out); - pr.waitFor(); - assertEquals(0, pr.exitValue()); prefs = new PreferencesMap(prefFile); assertEquals("preference should be saved", "xxx", prefs.get("test_pref")); @@ -131,29 +134,20 @@ public void testCommandLinePreferencesSave() throws Exception { @Test public void testCommandLineVersion() throws Exception { - Runtime rt = Runtime.getRuntime(); - Process pr = rt.exec(new String[]{ - arduinoPath.getAbsolutePath(), + Process pr = runArduino(false, true, null, new String[] { "--version", }); - pr.waitFor(); - Assertions.assertThat(pr.exitValue()) - .as("Process will finish with exit code 0 in --version") - .isEqualTo(0); Assertions.assertThat(new String(IOUtils.toByteArray(pr.getInputStream()))) .matches("Arduino: \\d+\\.\\d+\\.\\d+.*\r?\n"); } @Test public void testCommandLineMultipleAction() throws Exception { - Runtime rt = Runtime.getRuntime(); - Process pr = rt.exec(new String[]{ - arduinoPath.getAbsolutePath(), + Process pr = runArduino(true, false, null, new String[] { "--version", "--verify", }); - pr.waitFor(); Assertions.assertThat(pr.exitValue()) .as("Multiple Action will be rejected") From 71cdeeb9748dd4d7e6bd9f3528b4f5da6ffa46fa Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 6 May 2020 09:51:42 +0200 Subject: [PATCH 03/10] CommandLineTest: Produce less verbose output In one test, `--get-pref` is passed on the commandline to prevent starting the full GUI. When ran without arguments, `--get-pref` causes *all* preferences to be printed. Using `--version` achieves the same (no GUI is started) with just a single line of output. --- app/test/processing/app/CommandLineTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/test/processing/app/CommandLineTest.java b/app/test/processing/app/CommandLineTest.java index 0a7ea064ba3..f83c3d05391 100644 --- a/app/test/processing/app/CommandLineTest.java +++ b/app/test/processing/app/CommandLineTest.java @@ -111,7 +111,7 @@ public void testCommandLinePreferencesSave() throws Exception { runArduino(true, true, null, new String[] { "--save-prefs", "--preferences-file", prefFile.getAbsolutePath(), - "--get-pref", // avoids starting the GUI + "--version", // avoids starting the GUI }); runArduino(true, true, null, new String[] { From 687c27f1c8cdb55048378fccb2e7d0164af1d111 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 6 May 2020 10:55:40 +0200 Subject: [PATCH 04/10] CommandLineTest: Print commands being ran This makes the test output a bit more easier to read. --- app/test/processing/app/CommandLineTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/test/processing/app/CommandLineTest.java b/app/test/processing/app/CommandLineTest.java index f83c3d05391..ddbc3bada21 100644 --- a/app/test/processing/app/CommandLineTest.java +++ b/app/test/processing/app/CommandLineTest.java @@ -83,6 +83,8 @@ public Process runArduino(boolean output, boolean success, File wd, String[] ext args.add(arduinoPath.getAbsolutePath()); args.addAll(Arrays.asList(extraArgs)); + System.out.println("Running: " + String.join(" ", args)); + Process pr = rt.exec(args.toArray(new String[0]), null, wd); if (output) { IOUtils.copy(pr.getInputStream(), System.out); From af63ed16664014f4d07d4db117a0f8a0657b3a36 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 6 May 2020 20:59:24 +0200 Subject: [PATCH 05/10] AbstractGUITest: Make a subclass of AbstractWithPreferencesTest Both classes contained some duplicate code, so unify that by making one a subclass of the other. This also prepares for further additions that should be inherited by both. --- app/test/processing/app/AbstractGUITest.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/app/test/processing/app/AbstractGUITest.java b/app/test/processing/app/AbstractGUITest.java index d1db60d98aa..913508856a0 100644 --- a/app/test/processing/app/AbstractGUITest.java +++ b/app/test/processing/app/AbstractGUITest.java @@ -41,25 +41,21 @@ import javax.swing.*; import java.util.Random; -public abstract class AbstractGUITest { +public abstract class AbstractGUITest extends AbstractWithPreferencesTest { protected ArduinoFrameFixture window; @Before public void startUpTheIDE() throws Exception { + // This relies on AbstractWithPreferencesTest to set up the + // non-gui-specific stuff. + System.setProperty("mrj.version", "whynot"); //makes sense only on osx. See https://github.com/alexruiz/fest-swing-1.x/issues/2#issuecomment-86532042 - Runtime.getRuntime().addShutdownHook(new Thread(DeleteFilesOnShutdown.INSTANCE)); FailOnThreadViolationRepaintManager.install(); - BaseNoGui.initPlatform(); - BaseNoGui.getPlatform().init(); - PreferencesData.init(null); JPopupMenu.setDefaultLightWeightPopupEnabled(false); - Theme.init(); BaseNoGui.getPlatform().setLookAndFeel(); - Base.untitledFolder = FileUtils.createTempFolder("untitled" + new Random().nextInt(Integer.MAX_VALUE), ".tmp"); - DeleteFilesOnShutdown.add(Base.untitledFolder); window = GuiActionRunner.execute(new GuiQuery() { @Override From d10aaf40d35a70b03173831ac7c593ba5a7e9e3b Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 6 May 2020 20:28:53 +0200 Subject: [PATCH 06/10] AbstractWithPreferencesTest: Clean up files after every test Previously, this used the DeleteFilesOnShutdown class and a shutdown hook, which would delete the files only after shutdown. However, the shutdown handler would be re-added for every testcase, potentially leading to a lot of threads trying to delete the same files. This uses an alternative: Just keep a list of files to delete inside the testcase and use an @After handler to delete the files directly after each usecase. --- .../app/AbstractWithPreferencesTest.java | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/app/test/processing/app/AbstractWithPreferencesTest.java b/app/test/processing/app/AbstractWithPreferencesTest.java index f0d2f3a2b07..10a690393fd 100644 --- a/app/test/processing/app/AbstractWithPreferencesTest.java +++ b/app/test/processing/app/AbstractWithPreferencesTest.java @@ -29,17 +29,27 @@ package processing.app; -import cc.arduino.files.DeleteFilesOnShutdown; import org.junit.Before; +import org.junit.After; + import processing.app.helpers.FileUtils; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.util.Random; +import java.util.List; +import java.util.LinkedList; public abstract class AbstractWithPreferencesTest { + /** + * Files or directories that will be deleted after each test. + * Subclasses can add files here in @Test or @Before functions. + */ + protected List deleteAfter = new LinkedList(); @Before public void init() throws Exception { - Runtime.getRuntime().addShutdownHook(new Thread(DeleteFilesOnShutdown.INSTANCE)); BaseNoGui.initPlatform(); BaseNoGui.getPlatform().init(); PreferencesData.init(null); @@ -48,7 +58,13 @@ public void init() throws Exception { BaseNoGui.initPackages(); Base.untitledFolder = FileUtils.createTempFolder("untitled" + new Random().nextInt(Integer.MAX_VALUE), ".tmp"); - DeleteFilesOnShutdown.add(Base.untitledFolder); + deleteAfter.add(Base.untitledFolder); } + @After + public void cleanup() throws IOException { + for (File f : deleteAfter) + FileUtils.recursiveDelete(f); + deleteAfter = new LinkedList(); + } } From 6e850827b7ff844105659ec28cbefa93bf7ea6d0 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 6 May 2020 20:57:16 +0200 Subject: [PATCH 07/10] PreferencesData: Recreate PreferencesMap in init Normally, init is only called once during startup, so this does not add anything. However, when running the testsuite, PreferencesData could be initialized multiple times in a single test run. To prevent preferences from a previous test from interfering with subsequent tests, always start with a clean slate when calling init. --- arduino-core/src/processing/app/PreferencesData.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arduino-core/src/processing/app/PreferencesData.java b/arduino-core/src/processing/app/PreferencesData.java index 01f4568ad5b..11a250d689c 100644 --- a/arduino-core/src/processing/app/PreferencesData.java +++ b/arduino-core/src/processing/app/PreferencesData.java @@ -50,6 +50,9 @@ static public void init(File file) throws Exception { //ignore } + // Start with a clean slate + prefs = new PreferencesMap(); + // start by loading the defaults, in case something // important was deleted from the user prefs try { From 75e5c9eb43b9d2d8c8fe7a1aa110ed4c308dfd83 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 6 May 2020 10:29:31 +0200 Subject: [PATCH 08/10] Tests: Do not read system user's data The tests would initialize Base, PreferencesData with default settings and/or run the arduino executable, without specifying any settings to use. In practice, this would read settings from e.g. `~/.arduino15`, load libraries from the user's sketchbook, etc. This is not a good idea, since this can be influence the test. For example, the presence of invalid libraries would cause extra output to be generated, which breaks the `--version` test. Or having the "Use external editor" setting set would break tests that try to edit a sketch. This commit fixes this. The core of this commit is in `AbstractWithPreferencesTest`, which sets up a clean settings dir (to replace `~/.arduino15`) with a sketchbook and `preferences.txt` file inside before every test (and removes it again after the test. For some tests, this is enough, but some tests create an instance of `Base`, which again initializes everything, including preferences, from the default location. To prevent that, `--preferences-file` is passed to the `Base` constructor, loading the previously set up preferences. This is handled by the new `AbstractWithPreferencesTest.createBase()` method. Furthermore, CommandLineTest calls the actual Arduino executable, which has the same problem. This is fixed by passing the same `--preferences-file` option on the commandline (generated by `AbstractWithPreferencesTest.getBaseArgs()`). This should prevent all tests from reading the the default settings files, fixing some tests on my system and even speeding up the tests somewhat (due less libraries and cores to load, probably). --- app/test/processing/app/AbstractGUITest.java | 2 +- .../app/AbstractWithPreferencesTest.java | 46 ++++++++++++++++++- app/test/processing/app/CommandLineTest.java | 9 +++- .../processing/app/DefaultTargetTest.java | 2 +- 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/app/test/processing/app/AbstractGUITest.java b/app/test/processing/app/AbstractGUITest.java index 913508856a0..37f0ebefe58 100644 --- a/app/test/processing/app/AbstractGUITest.java +++ b/app/test/processing/app/AbstractGUITest.java @@ -60,7 +60,7 @@ public void startUpTheIDE() throws Exception { window = GuiActionRunner.execute(new GuiQuery() { @Override protected ArduinoFrameFixture executeInEDT() throws Throwable { - return new ArduinoFrameFixture(new Base(new String[0]).editors.get(0)); + return new ArduinoFrameFixture(createBase().editors.get(0)); } }); } diff --git a/app/test/processing/app/AbstractWithPreferencesTest.java b/app/test/processing/app/AbstractWithPreferencesTest.java index 10a690393fd..0045ee5fc28 100644 --- a/app/test/processing/app/AbstractWithPreferencesTest.java +++ b/app/test/processing/app/AbstractWithPreferencesTest.java @@ -29,6 +29,7 @@ package processing.app; +import static org.junit.Assert.assertEquals; import org.junit.Before; import org.junit.After; @@ -47,12 +48,30 @@ public abstract class AbstractWithPreferencesTest { * Subclasses can add files here in @Test or @Before functions. */ protected List deleteAfter = new LinkedList(); + protected File preferencesFile; @Before public void init() throws Exception { + File settingsDir = Files.createTempDirectory("arduino_test_settings").toFile(); + deleteAfter.add(settingsDir); + + preferencesFile = new File(settingsDir, "preferences.txt"); + File sketchbookDir = new File(settingsDir, "sketchbook"); + sketchbookDir.mkdir(); + BaseNoGui.initPlatform(); BaseNoGui.getPlatform().init(); - PreferencesData.init(null); + + PreferencesData.init(preferencesFile); + // Do not read anything from e.g. ~/.arduino15 + PreferencesData.set("settings.path", settingsDir.toString()); + // Do not read or write the default ~/Arduino sketchbook + PreferencesData.set("sketchbook.path", sketchbookDir.toString()); + // Write the defaults, with these changes to file. This allows them + // to be reloaded when creating a Base instance (see getBaseArgs() + // below). + PreferencesData.save(); + Theme.init(); BaseNoGui.initPackages(); @@ -61,6 +80,31 @@ public void init() throws Exception { deleteAfter.add(Base.untitledFolder); } + /** + * Returns arguments to be passed to the Base constructor or on the + * commandline to set up the created dummy environment. + */ + protected String[] getBaseArgs() { + return new String[] { + // Preferences are loaded (using --preferences-file) before + // processing any other commandline options (e.g. --pref), so only + // use --preferences-file here. Also, this does not affect the + // "action" mode, for tests that require the GUI to be loaded. + "--preferences-file", preferencesFile.toString(), + }; + } + + /** + * Creates a new instance of Base. Always use this rather than calling + * it directly, to ensure the right settings are used. + */ + protected Base createBase() throws Exception { + Base base = new Base(getBaseArgs()); + // Doublecheck that the right preferencesFile was loaded + assertEquals(preferencesFile, PreferencesData.preferencesFile); + return base; + } + @After public void cleanup() throws IOException { for (File f : deleteAfter) diff --git a/app/test/processing/app/CommandLineTest.java b/app/test/processing/app/CommandLineTest.java index ddbc3bada21..1db6afe12f1 100644 --- a/app/test/processing/app/CommandLineTest.java +++ b/app/test/processing/app/CommandLineTest.java @@ -45,7 +45,13 @@ import processing.app.helpers.OSUtils; import processing.app.helpers.PreferencesMap; -public class CommandLineTest { +/** + * This extends AbstractWithPreferencesTest which initializes part of + * the internal Arduino structures. Most of that is not required, but it + * also conveniently sets up a settings directory and preferences file, + * which we can use here (through getBaseArgs()). + */ +public class CommandLineTest extends AbstractWithPreferencesTest { private static File buildPath; private static File arduinoPath; @@ -81,6 +87,7 @@ public Process runArduino(boolean output, boolean success, File wd, String[] ext List args = new ArrayList(); args.add(arduinoPath.getAbsolutePath()); + args.addAll(Arrays.asList(getBaseArgs())); args.addAll(Arrays.asList(extraArgs)); System.out.println("Running: " + String.join(" ", args)); diff --git a/app/test/processing/app/DefaultTargetTest.java b/app/test/processing/app/DefaultTargetTest.java index 37819c84cff..24767bee30d 100644 --- a/app/test/processing/app/DefaultTargetTest.java +++ b/app/test/processing/app/DefaultTargetTest.java @@ -57,7 +57,7 @@ public void testDefaultTarget() throws Exception { PreferencesData.set("board", "unreal_board"); // should not raise an exception - new Base(new String[0]); + createBase(); // skip test if no target platforms are available Assume.assumeNotNull(BaseNoGui.getTargetPlatform()); From d0aa72f6ae5fb1d41f309a334b3c9d8dd17e0d5e Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 6 May 2020 21:41:41 +0200 Subject: [PATCH 09/10] HittingEscapeOnCloseConfirmationDialogTest: Match dialog by title This would match the "Close" dialog, by looking for any JDialog on screen. However, in some cases, there could be a second dialog (I have seen a "Install this package to use your xxx board" popup because I happened to have an Arduino Zero connected, presumably an "Updates are available" popup could cause this as well). By matching not just the type of the dialog, but also the title, only one dialog is matched and the testcase runs more reliably. --- .../app/HittingEscapeOnCloseConfirmationDialogTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/test/processing/app/HittingEscapeOnCloseConfirmationDialogTest.java b/app/test/processing/app/HittingEscapeOnCloseConfirmationDialogTest.java index 83897b1963c..59dff4c3595 100644 --- a/app/test/processing/app/HittingEscapeOnCloseConfirmationDialogTest.java +++ b/app/test/processing/app/HittingEscapeOnCloseConfirmationDialogTest.java @@ -30,6 +30,7 @@ package processing.app; import org.fest.swing.core.KeyPressInfo; +import org.fest.swing.core.matcher.DialogMatcher; import org.fest.swing.finder.WindowFinder; import org.fest.swing.fixture.DialogFixture; import org.junit.Test; @@ -39,6 +40,7 @@ import java.awt.event.KeyEvent; import static org.junit.Assert.assertEquals; +import static processing.app.I18n.tr; public class HittingEscapeOnCloseConfirmationDialogTest extends AbstractGUITest { @@ -49,7 +51,8 @@ public void shouldJustCloseTheDialog() throws Exception { window.close(); - DialogFixture dialog = WindowFinder.findDialog(JDialog.class).using(window.robot); + DialogMatcher matcher = DialogMatcher.withTitle(tr("Close")).andShowing(); + DialogFixture dialog = WindowFinder.findDialog(matcher).using(window.robot); dialog.pressAndReleaseKey(KeyPressInfo.keyCode(KeyEvent.VK_ESCAPE)); EditorConsole console = (EditorConsole) window.scrollPane("console").component(); From 647328493168c7a6e629d1e40102864def702d82 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 6 May 2020 21:44:54 +0200 Subject: [PATCH 10/10] AbstractWithPreferencesTest: Disable update checks This prevents the Arduino instance in tests from checking for updates. I have not seen any problems resulting from this, but disabling network requests is generally a good idea in tests (to prevent external factors from influencing the test results). In addition to update checks, there is also the CloudBoardResolver that could do network requests, but there does not seem to be a preference for disabling that. --- app/test/processing/app/AbstractWithPreferencesTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/test/processing/app/AbstractWithPreferencesTest.java b/app/test/processing/app/AbstractWithPreferencesTest.java index 0045ee5fc28..1075aebda4f 100644 --- a/app/test/processing/app/AbstractWithPreferencesTest.java +++ b/app/test/processing/app/AbstractWithPreferencesTest.java @@ -67,6 +67,8 @@ public void init() throws Exception { PreferencesData.set("settings.path", settingsDir.toString()); // Do not read or write the default ~/Arduino sketchbook PreferencesData.set("sketchbook.path", sketchbookDir.toString()); + // Do not perform any update checks + PreferencesData.set("update.check", sketchbookDir.toString()); // Write the defaults, with these changes to file. This allows them // to be reloaded when creating a Base instance (see getBaseArgs() // below).