diff --git a/.github/workflows/ant.yml b/.github/workflows/ant.yml
deleted file mode 100644
index 0a2e0a343e6..00000000000
--- a/.github/workflows/ant.yml
+++ /dev/null
@@ -1,34 +0,0 @@
-name: Java CI
-
-on:
-  push:
-  pull_request:
-
-jobs:
-  build:
-
-    runs-on: ubuntu-latest
-
-    steps:
-    - uses: actions/checkout@v2
-    - name: Set up JDK 1.8
-      uses: actions/setup-java@v1
-      with:
-        java-version: 1.8
-    - name: Build with Ant
-      working-directory: ./build
-      run: |
-        sed -i 's#<input .*/>##' build.xml
-        ant clean dist
-    - name: Install X virtual framebuffer
-      run: sudo apt-get install -y xvfb
-    - name: Run tests
-      working-directory: ./app
-      run: xvfb-run --auto-servernum --server-args "-screen 0 1024x768x24" ant test -Drunning-from-github-action=1
-    - name: Publish results
-      uses: actions/upload-artifact@v1
-      with:
-        name: html-results
-        path: app/test-bin/results/html/
-    - name: Cleanup xvfb
-      uses: bcomnes/cleanup-xvfb@v1
diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml
new file mode 100644
index 00000000000..0ad5b04b42c
--- /dev/null
+++ b/.github/workflows/unit-test.yml
@@ -0,0 +1,58 @@
+name: Arduino IDE unit test run
+
+on:
+  push:
+  pull_request:
+
+jobs:
+  test-linux:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+    - name: Set up JDK 1.8
+      uses: actions/setup-java@v1
+      with:
+        java-version: 1.8
+    - name: Build with Ant
+      working-directory: ./build
+      run: ant clean build
+    - name: Install X virtual framebuffer
+      run: sudo apt-get install -y xvfb
+    - name: Run tests
+      working-directory: ./build
+      run: xvfb-run --auto-servernum --server-args "-screen 0 1024x768x24" ant test -Drunning-from-github-action=1
+    - name: Publish results
+      if: always()
+      uses: actions/upload-artifact@v2
+      with:
+        name: html-results-linux
+        path: app/test-bin/results/html/
+    - name: Cleanup xvfb
+      uses: bcomnes/cleanup-xvfb@v1
+
+  test-windows:
+    runs-on: windows-latest
+    steps:
+    - uses: actions/checkout@v2
+    - name: Download JDK 1.8 for Windows/x86
+      run: curl -O "https://cdn.azul.com/zulu/bin/zulu8.46.0.19-ca-jdk8.0.252-win_i686.zip"
+    - name: Set up JDK 1.8
+      uses: actions/setup-java@master
+      with:
+        version: 1.8
+        architecture: x86
+        jdkFile: ./zulu8.46.0.19-ca-jdk8.0.252-win_i686.zip
+    - name: Build with Ant
+      working-directory: ./build
+      run: |
+        java -version
+        ant clean build
+    - name: Run tests
+      working-directory: ./build
+      run: ant test -Drunning-from-github-action=1
+    - name: Publish results
+      if: always()
+      uses: actions/upload-artifact@v2
+      with:
+        name: html-results-win
+        path: app/test-bin/results/html/
diff --git a/app/.classpath b/app/.classpath
index bb2bf7417c6..b3bd74a592a 100644
--- a/app/.classpath
+++ b/app/.classpath
@@ -45,14 +45,16 @@
 	<classpathentry kind="lib" path="lib/xml-apis-1.3.04.jar"/>
 	<classpathentry kind="lib" path="lib/xml-apis-ext-1.3.04.jar"/>
 	<classpathentry kind="lib" path="lib/xmlgraphics-commons-2.0.jar"/>
-	<classpathentry kind="lib" path="test-lib/junit-4.11.jar"/>
-	<classpathentry kind="lib" path="test-lib/fest-assert-1.2.jar"/>
-	<classpathentry kind="lib" path="test-lib/fest-reflect-1.2.jar"/>
-	<classpathentry kind="lib" path="test-lib/fest-swing-1.2.jar"/>
-	<classpathentry kind="lib" path="test-lib/fest-util-1.1.2.jar"/>
 	<classpathentry kind="lib" path="test-lib/jcip-annotations-1.0.jar"/>
 	<classpathentry kind="lib" path="lib/jtouchbar-1.0.0.jar"/>
 	<classpathentry kind="lib" path="lib/commons-lang3-3.8.1.jar"/>
 	<classpathentry kind="lib" path="lib/jssc-2.8.0-arduino4.jar"/>
+	<classpathentry kind="lib" path="test-lib/assertj-swing-junit-3.9.2.jar"/>
+	<classpathentry kind="lib" path="test-lib/assertj-core-1.7.1.jar"/>
+	<classpathentry kind="lib" path="test-lib/assertj-swing-1.2.0.jar"/>
+	<classpathentry kind="lib" path="test-lib/assertj-swing-junit-1.2.0.jar"/>
+	<classpathentry kind="lib" path="test-lib/assertj-swing-junit-4.5-1.2.0.jar"/>
+	<classpathentry kind="lib" path="test-lib/fest-util-1.2.5.jar"/>
+	<classpathentry kind="lib" path="test-lib/junit-4.5.jar"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/app/build.xml b/app/build.xml
index fa3223642ff..8c8f586d29f 100644
--- a/app/build.xml
+++ b/app/build.xml
@@ -123,6 +123,7 @@
     <fail message="Need single-test-class if single-test-methods is set" if="single-test-methods" unless="single-test-class"/>
 
     <junit printsummary="yes" dir="${work.dir}" fork="true" showoutput="no" failureproperty="test.failed">
+      <jvmarg value="-Dlog4j.dir=."/>
       <jvmarg value="-Djava.library.path=${java.additional.library.path}"/>
       <jvmarg value="-DWORK_DIR=."/>
       <jvmarg value="-ea"/>
@@ -135,10 +136,8 @@
       </classpath>
 
       <!-- Write XML files (for report-generation) and TXT files (for manual review) for every test class -->
-      <formatter type="plain" />
+      <formatter type="plain" usefile="false" /> <!-- to screen -->
       <formatter type="xml"/>
-      <!-- Print full details to stdout when running a single test -->
-      <formatter type="plain" usefile="false" if="single-test-class"/>
       <!-- When running all tests, print details for failing tests and a summary for each test class (printsummary=yes above) -->
       <formatter type="brief" usefile="false" unless="single-test-class"/>
 
diff --git a/app/src/processing/app/EditorConsole.java b/app/src/processing/app/EditorConsole.java
index 3942908a1da..380c8c61c9e 100644
--- a/app/src/processing/app/EditorConsole.java
+++ b/app/src/processing/app/EditorConsole.java
@@ -231,7 +231,13 @@ public void insertString(String str, SimpleAttributeSet attributes) throws BadLo
   }
 
   public String getText() {
-    return consoleTextPane.getText();
+    try {
+      return document.getText(0, document.getLength());
+    } catch (BadLocationException e) {
+      // Should never happen...
+      e.printStackTrace();
+      return "";
+    }
   }
 
 }
diff --git a/app/test-lib/assertj-core-1.7.1.jar b/app/test-lib/assertj-core-1.7.1.jar
new file mode 100644
index 00000000000..ee9268db750
Binary files /dev/null and b/app/test-lib/assertj-core-1.7.1.jar differ
diff --git a/app/test-lib/assertj-swing-1.2.0.jar b/app/test-lib/assertj-swing-1.2.0.jar
new file mode 100644
index 00000000000..4d37adf145d
Binary files /dev/null and b/app/test-lib/assertj-swing-1.2.0.jar differ
diff --git a/app/test-lib/assertj-swing-junit-1.2.0.jar b/app/test-lib/assertj-swing-junit-1.2.0.jar
new file mode 100644
index 00000000000..ba24a065292
Binary files /dev/null and b/app/test-lib/assertj-swing-junit-1.2.0.jar differ
diff --git a/app/test-lib/assertj-swing-junit-3.9.2.jar b/app/test-lib/assertj-swing-junit-3.9.2.jar
new file mode 100644
index 00000000000..c0635d0c272
Binary files /dev/null and b/app/test-lib/assertj-swing-junit-3.9.2.jar differ
diff --git a/app/test-lib/assertj-swing-junit-4.5-1.2.0.jar b/app/test-lib/assertj-swing-junit-4.5-1.2.0.jar
new file mode 100644
index 00000000000..56003642948
Binary files /dev/null and b/app/test-lib/assertj-swing-junit-4.5-1.2.0.jar differ
diff --git a/app/test-lib/fest-assert-1.2.jar b/app/test-lib/fest-assert-1.2.jar
deleted file mode 100644
index dcd667e5356..00000000000
Binary files a/app/test-lib/fest-assert-1.2.jar and /dev/null differ
diff --git a/app/test-lib/fest-reflect-1.2.jar b/app/test-lib/fest-reflect-1.2.jar
deleted file mode 100644
index d33ddb65349..00000000000
Binary files a/app/test-lib/fest-reflect-1.2.jar and /dev/null differ
diff --git a/app/test-lib/fest-swing-1.2.jar b/app/test-lib/fest-swing-1.2.jar
deleted file mode 100644
index 054c368c226..00000000000
Binary files a/app/test-lib/fest-swing-1.2.jar and /dev/null differ
diff --git a/app/test-lib/fest-util-1.1.2.jar b/app/test-lib/fest-util-1.1.2.jar
deleted file mode 100644
index c5f608bfabb..00000000000
Binary files a/app/test-lib/fest-util-1.1.2.jar and /dev/null differ
diff --git a/app/test-lib/fest-util-1.2.5.jar b/app/test-lib/fest-util-1.2.5.jar
new file mode 100644
index 00000000000..e0215f8dcc1
Binary files /dev/null and b/app/test-lib/fest-util-1.2.5.jar differ
diff --git a/app/test-lib/fest.LICENSE.ASL-2.0.txt b/app/test-lib/fest.LICENSE.ASL-2.0.txt
deleted file mode 100644
index d6456956733..00000000000
--- a/app/test-lib/fest.LICENSE.ASL-2.0.txt
+++ /dev/null
@@ -1,202 +0,0 @@
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
diff --git a/app/test-lib/junit-4.11.jar b/app/test-lib/junit-4.11.jar
deleted file mode 100644
index 4d552a6f3fd..00000000000
Binary files a/app/test-lib/junit-4.11.jar and /dev/null differ
diff --git a/app/test-lib/junit-4.5.jar b/app/test-lib/junit-4.5.jar
new file mode 100644
index 00000000000..733921623d4
Binary files /dev/null and b/app/test-lib/junit-4.5.jar differ
diff --git a/app/test/cc/arduino/contributions/.gitattributes b/app/test/cc/arduino/contributions/.gitattributes
new file mode 100644
index 00000000000..ec70fab0357
--- /dev/null
+++ b/app/test/cc/arduino/contributions/.gitattributes
@@ -0,0 +1 @@
+package_index.json*	-text
diff --git a/app/test/processing/app/AbstractGUITest.java b/app/test/processing/app/AbstractGUITest.java
index 37f0ebefe58..b2a829a821c 100644
--- a/app/test/processing/app/AbstractGUITest.java
+++ b/app/test/processing/app/AbstractGUITest.java
@@ -29,17 +29,15 @@
 
 package processing.app;
 
-import cc.arduino.files.DeleteFilesOnShutdown;
-import org.fest.swing.edt.FailOnThreadViolationRepaintManager;
-import org.fest.swing.edt.GuiActionRunner;
-import org.fest.swing.edt.GuiQuery;
+import javax.swing.JPopupMenu;
+
+import org.assertj.swing.edt.FailOnThreadViolationRepaintManager;
+import org.assertj.swing.edt.GuiActionRunner;
+import org.assertj.swing.edt.GuiQuery;
 import org.junit.After;
 import org.junit.Before;
-import processing.app.helpers.ArduinoFrameFixture;
-import processing.app.helpers.FileUtils;
 
-import javax.swing.*;
-import java.util.Random;
+import processing.app.helpers.ArduinoFrameFixture;
 
 public abstract class AbstractGUITest extends AbstractWithPreferencesTest {
 
diff --git a/app/test/processing/app/AutoformatProducesOneUndoActionTest.java b/app/test/processing/app/AutoformatProducesOneUndoActionTest.java
index 33314b4a197..aa4e7513c46 100644
--- a/app/test/processing/app/AutoformatProducesOneUndoActionTest.java
+++ b/app/test/processing/app/AutoformatProducesOneUndoActionTest.java
@@ -29,12 +29,13 @@
 
 package processing.app;
 
-import org.fest.swing.fixture.JMenuItemFixture;
 import org.junit.Test;
 import processing.app.helpers.SketchTextAreaFixture;
 
 import static org.junit.Assert.assertEquals;
 
+import org.assertj.swing.fixture.JMenuItemFixture;
+
 public class AutoformatProducesOneUndoActionTest extends AbstractGUITest {
 
   public static final String SOURCE_BEFORE = "void setup() {\n" +
diff --git a/app/test/processing/app/AutoformatSavesCaretPositionTest.java b/app/test/processing/app/AutoformatSavesCaretPositionTest.java
index fb311707d53..81557967a93 100644
--- a/app/test/processing/app/AutoformatSavesCaretPositionTest.java
+++ b/app/test/processing/app/AutoformatSavesCaretPositionTest.java
@@ -29,11 +29,12 @@
 
 package processing.app;
 
-import org.fest.swing.fixture.JMenuItemFixture;
+import static org.junit.Assert.assertEquals;
+
+import org.assertj.swing.fixture.JMenuItemFixture;
 import org.junit.Test;
-import processing.app.helpers.SketchTextAreaFixture;
 
-import static org.junit.Assert.assertEquals;
+import processing.app.helpers.SketchTextAreaFixture;
 
 public class AutoformatSavesCaretPositionTest extends AbstractGUITest {
 
diff --git a/app/test/processing/app/AutoformatTest.java b/app/test/processing/app/AutoformatTest.java
index 2b87b17571c..aaf0c1c8182 100644
--- a/app/test/processing/app/AutoformatTest.java
+++ b/app/test/processing/app/AutoformatTest.java
@@ -29,11 +29,12 @@
 
 package processing.app;
 
-import org.fest.swing.fixture.JMenuItemFixture;
+import static org.junit.Assert.assertEquals;
+
+import org.assertj.swing.fixture.JMenuItemFixture;
 import org.junit.Test;
-import processing.app.helpers.SketchTextAreaFixture;
 
-import static org.junit.Assert.assertEquals;
+import processing.app.helpers.SketchTextAreaFixture;
 
 public class AutoformatTest extends AbstractGUITest {
 
diff --git a/app/test/processing/app/BlockCommentGeneratesOneUndoActionTest.java b/app/test/processing/app/BlockCommentGeneratesOneUndoActionTest.java
index 8607160775e..b005625c8df 100644
--- a/app/test/processing/app/BlockCommentGeneratesOneUndoActionTest.java
+++ b/app/test/processing/app/BlockCommentGeneratesOneUndoActionTest.java
@@ -33,9 +33,9 @@
 
 import java.awt.Frame;
 
-import org.fest.swing.edt.GuiActionRunner;
-import org.fest.swing.edt.GuiQuery;
-import org.fest.swing.fixture.JMenuItemFixture;
+import org.assertj.swing.edt.GuiActionRunner;
+import org.assertj.swing.edt.GuiQuery;
+import org.assertj.swing.fixture.JMenuItemFixture;
 import org.junit.Test;
 
 import processing.app.helpers.SketchTextAreaFixture;
diff --git a/app/test/processing/app/CommandLineTest.java b/app/test/processing/app/CommandLineTest.java
index 1db6afe12f1..5df7307ae47 100644
--- a/app/test/processing/app/CommandLineTest.java
+++ b/app/test/processing/app/CommandLineTest.java
@@ -29,16 +29,20 @@
 
 package processing.app;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.List;
 import java.util.ArrayList;
 import java.util.Arrays;
 
 import org.apache.commons.compress.utils.IOUtils;
-import org.fest.assertions.Assertions;
+import org.assertj.core.api.Assertions;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -70,7 +74,7 @@ public static void findBuildPaths() throws Exception {
       arduinoPath = new File(buildPath, "build/linux/work/arduino");
     }
     if (OSUtils.isWindows()) {
-      arduinoPath = new File(buildPath, "build/windows/work/arduino.exe");
+      arduinoPath = new File(buildPath, "build/windows/work/arduino_debug.exe");
     }
     if (OSUtils.isMacOS()) {
       arduinoPath = new File(buildPath,
@@ -82,10 +86,22 @@ 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 {
+  private Thread consume(InputStream s, OutputStream out) {
+    Thread t = new Thread(() -> {
+      try {
+        IOUtils.copy(s, out);
+      } catch (IOException e) {
+        e.printStackTrace();
+      }
+    });
+    t.start();
+    return t;
+  }
+
+  public Process runArduino(OutputStream output, boolean success, File wd, String[] extraArgs) throws IOException, InterruptedException {
     Runtime rt = Runtime.getRuntime();
 
-    List<String> args = new ArrayList<String>();
+    List<String> args = new ArrayList<>();
     args.add(arduinoPath.getAbsolutePath());
     args.addAll(Arrays.asList(getBaseArgs()));
     args.addAll(Arrays.asList(extraArgs));
@@ -93,11 +109,11 @@ public Process runArduino(boolean output, boolean success, File wd, String[] ext
     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);
-      IOUtils.copy(pr.getErrorStream(), System.out);
-    }
+    Thread outThread = consume(pr.getInputStream(), output);
+    Thread errThread = consume(pr.getErrorStream(), System.err);
     pr.waitFor();
+    outThread.join(5000);
+    errThread.join(5000);
     if (success)
       assertEquals(0, pr.exitValue());
     return pr;
@@ -106,7 +122,7 @@ public Process runArduino(boolean output, boolean success, File wd, String[] ext
   @Test
   public void testCommandLineBuildWithRelativePath() throws Exception {
     File wd = new File(buildPath, "build/shared/examples/01.Basics/Blink/");
-    runArduino(true, true, wd, new String[] {
+    runArduino(System.out, true, wd, new String[] {
         "--board", "arduino:avr:uno",
         "--verify", "Blink.ino",
     });
@@ -117,13 +133,13 @@ public void testCommandLinePreferencesSave() throws Exception {
     File prefFile = File.createTempFile("test_pref", ".txt");
     prefFile.deleteOnExit();
 
-    runArduino(true, true, null, new String[] {
+    runArduino(System.out, true, null, new String[] {
         "--save-prefs",
         "--preferences-file", prefFile.getAbsolutePath(),
         "--version", // avoids starting the GUI
     });
 
-    runArduino(true, true, null, new String[] {
+    runArduino(System.out, true, null, new String[] {
         "--pref", "test_pref=xxx",
         "--preferences-file", prefFile.getAbsolutePath(),
     });
@@ -131,7 +147,7 @@ public void testCommandLinePreferencesSave() throws Exception {
     PreferencesMap prefs = new PreferencesMap(prefFile);
     assertNull("preference should not be saved", prefs.get("test_pref"));
 
-    runArduino(true, true, null, new String[] {
+    runArduino(System.out, true, null, new String[] {
         "--pref", "test_pref=xxx",
         "--preferences-file", prefFile.getAbsolutePath(),
         "--save-prefs",
@@ -143,17 +159,18 @@ public void testCommandLinePreferencesSave() throws Exception {
 
   @Test
   public void testCommandLineVersion() throws Exception {
-    Process pr = runArduino(false, true, null, new String[] {
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    runArduino(out, true, null, new String[] {
       "--version",
     });
 
-    Assertions.assertThat(new String(IOUtils.toByteArray(pr.getInputStream())))
-        .matches("Arduino: \\d+\\.\\d+\\.\\d+.*\r?\n");
+    Assertions.assertThat(out.toString())
+        .matches("(?m)Arduino: \\d+\\.\\d+\\.\\d+.*\r?\n");
   }
 
   @Test
   public void testCommandLineMultipleAction() throws Exception {
-    Process pr = runArduino(true, false, null, new String[] {
+    Process pr = runArduino(System.out, false, null, new String[] {
       "--version",
       "--verify",
     });
diff --git a/app/test/processing/app/DefaultTargetTest.java b/app/test/processing/app/DefaultTargetTest.java
index 24767bee30d..ed702202471 100644
--- a/app/test/processing/app/DefaultTargetTest.java
+++ b/app/test/processing/app/DefaultTargetTest.java
@@ -29,13 +29,14 @@
 
 package processing.app;
 
+import static org.junit.Assert.assertFalse;
+
 import org.junit.After;
 import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 
 import processing.app.debug.TargetBoard;
-import static org.junit.Assert.assertNotEquals;
 
 public class DefaultTargetTest extends AbstractWithPreferencesTest {
 
@@ -63,6 +64,6 @@ public void testDefaultTarget() throws Exception {
     Assume.assumeNotNull(BaseNoGui.getTargetPlatform());
     
     TargetBoard targetBoard = BaseNoGui.getTargetBoard();
-    assertNotEquals("unreal_board", targetBoard.getId());
+    assertFalse("unreal_board".equals(targetBoard.getId()));
   }
 }
diff --git a/app/test/processing/app/HittingEscapeOnCloseConfirmationDialogTest.java b/app/test/processing/app/HittingEscapeOnCloseConfirmationDialogTest.java
index 59dff4c3595..20e32dc830f 100644
--- a/app/test/processing/app/HittingEscapeOnCloseConfirmationDialogTest.java
+++ b/app/test/processing/app/HittingEscapeOnCloseConfirmationDialogTest.java
@@ -29,20 +29,21 @@
 
 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;
-import processing.app.helpers.SketchTextAreaFixture;
+import static org.junit.Assert.assertEquals;
+import static processing.app.I18n.tr;
 
-import javax.swing.*;
 import java.awt.event.KeyEvent;
 
-import static org.junit.Assert.assertEquals;
-import static processing.app.I18n.tr;
+import org.assertj.swing.core.KeyPressInfo;
+import org.assertj.swing.core.matcher.DialogMatcher;
+import org.assertj.swing.fixture.DialogFixture;
+import org.junit.Test;
+
+import processing.app.helpers.SketchTextAreaFixture;
 
-public class HittingEscapeOnCloseConfirmationDialogTest extends AbstractGUITest {
+
+public class HittingEscapeOnCloseConfirmationDialogTest
+    extends AbstractGUITest {
 
   @Test
   public void shouldJustCloseTheDialog() throws Exception {
@@ -52,11 +53,10 @@ public void shouldJustCloseTheDialog() throws Exception {
     window.close();
 
     DialogMatcher matcher = DialogMatcher.withTitle(tr("Close")).andShowing();
-    DialogFixture dialog = WindowFinder.findDialog(matcher).using(window.robot);
+    DialogFixture dialog = window.findJDialog(matcher);
     dialog.pressAndReleaseKey(KeyPressInfo.keyCode(KeyEvent.VK_ESCAPE));
 
-    EditorConsole console = (EditorConsole) window.scrollPane("console").component();
-
+    EditorConsole console = window.getEditor().console;
     assertEquals("", console.getText());
   }
 }
diff --git a/app/test/processing/app/ReduceIndentWith1CharOnLastLineTest.java b/app/test/processing/app/ReduceIndentWith1CharOnLastLineTest.java
index 81c46754311..bbc1828a5b1 100644
--- a/app/test/processing/app/ReduceIndentWith1CharOnLastLineTest.java
+++ b/app/test/processing/app/ReduceIndentWith1CharOnLastLineTest.java
@@ -31,7 +31,7 @@
 
 import static org.junit.Assert.assertEquals;
 
-import org.fest.swing.fixture.JMenuItemFixture;
+import org.assertj.swing.fixture.JMenuItemFixture;
 import org.junit.Test;
 
 import processing.app.helpers.SketchTextAreaFixture;
@@ -50,8 +50,7 @@ public void shouldJustCloseTheDialog() throws Exception {
     menuDecreaseIndent.requireEnabled();
     menuDecreaseIndent.click();
 
-    EditorConsole console = (EditorConsole) window.scrollPane("console").component();
-
+    EditorConsole console = window.getEditor().console;
     assertEquals("", console.getText());
   }
 }
diff --git a/app/test/processing/app/ReplacingTextGeneratesTwoUndoActionsTest.java b/app/test/processing/app/ReplacingTextGeneratesTwoUndoActionsTest.java
index f1ebe4a9778..c99c3904453 100644
--- a/app/test/processing/app/ReplacingTextGeneratesTwoUndoActionsTest.java
+++ b/app/test/processing/app/ReplacingTextGeneratesTwoUndoActionsTest.java
@@ -29,11 +29,12 @@
 
 package processing.app;
 
-import org.fest.swing.fixture.JMenuItemFixture;
+import static org.junit.Assert.assertEquals;
+
+import org.assertj.swing.fixture.JMenuItemFixture;
 import org.junit.Test;
-import processing.app.helpers.SketchTextAreaFixture;
 
-import static org.junit.Assert.assertEquals;
+import processing.app.helpers.SketchTextAreaFixture;
 
 public class ReplacingTextGeneratesTwoUndoActionsTest extends AbstractGUITest {
 
diff --git a/app/test/processing/app/SerialTest.java b/app/test/processing/app/SerialTest.java
index 63280811e24..fc5f28e20a3 100644
--- a/app/test/processing/app/SerialTest.java
+++ b/app/test/processing/app/SerialTest.java
@@ -51,8 +51,16 @@ protected void message(char[] chars, int length) {
   public void testSerialUTF8Decoder() throws Exception {
     NullSerial s = new NullSerial();
     // https://github.com/arduino/Arduino/issues/9808
-    String testdata = "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789°0123456789";
-    s.processSerialEvent(testdata.getBytes());
-    assertEquals(s.output, testdata);
+    byte testData[] = { 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51,
+        52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49,
+        50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
+        48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55,
+        56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53,
+        54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51,
+        52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, //
+        -62, -80, // UTF8 char
+        48, 49, 50, 51, 52, 53, 54, 55, 56, 57 };
+    s.processSerialEvent(testData);
+    assertEquals(new String(testData, "UTF-8"), s.output);
   }
 }
diff --git a/app/test/processing/app/UpdateTextAreaActionTest.java b/app/test/processing/app/UpdateTextAreaActionTest.java
index b32ea1850be..43de96751a2 100644
--- a/app/test/processing/app/UpdateTextAreaActionTest.java
+++ b/app/test/processing/app/UpdateTextAreaActionTest.java
@@ -1,6 +1,6 @@
 package processing.app;
 
-import static org.fest.assertions.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThat;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/app/test/processing/app/helpers/ArduinoFrameFixture.java b/app/test/processing/app/helpers/ArduinoFrameFixture.java
index 9f597e27b48..3a3bf721744 100644
--- a/app/test/processing/app/helpers/ArduinoFrameFixture.java
+++ b/app/test/processing/app/helpers/ArduinoFrameFixture.java
@@ -29,7 +29,15 @@
 
 package processing.app.helpers;
 
-import org.fest.swing.fixture.FrameFixture;
+import java.awt.Component;
+
+import javax.swing.JDialog;
+
+import org.assertj.swing.core.matcher.DialogMatcher;
+import org.assertj.swing.finder.WindowFinder;
+import org.assertj.swing.fixture.DialogFixture;
+import org.assertj.swing.fixture.FrameFixture;
+
 import processing.app.Editor;
 import processing.app.syntax.SketchTextArea;
 
@@ -43,10 +51,20 @@ public ArduinoFrameFixture(Editor editor) {
   }
 
   public SketchTextAreaFixture textArea(String name) {
-    return new SketchTextAreaFixture(robot, (SketchTextArea) this.robot.finder().find(new SketchTextAreaComponentMatcher(name)));
+    Component comp = robot().finder()
+        .find(c -> c instanceof SketchTextArea && name.equals(c.getName()));
+    return new SketchTextAreaFixture(robot(), (SketchTextArea) comp);
   }
 
   public Editor getEditor() {
     return editor;
   }
+
+  public DialogFixture findJDialog() {
+    return WindowFinder.findDialog(JDialog.class).using(robot());
+  }
+
+  public DialogFixture findJDialog(DialogMatcher matcher) {
+    return WindowFinder.findDialog(matcher).using(robot());
+  }
 }
diff --git a/app/test/processing/app/helpers/SketchTextAreaComponentDriver.java b/app/test/processing/app/helpers/SketchTextAreaComponentDriver.java
deleted file mode 100644
index 7d569c3bf4d..00000000000
--- a/app/test/processing/app/helpers/SketchTextAreaComponentDriver.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * This file is part of Arduino.
- *
- * Copyright 2015 Ricardo JL Rufino (ricardo@criativasoft.com.br)
- * Copyright 2015 Arduino LLC
- *
- * Arduino is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
- *
- * As a special exception, you may use this file as part of a free software
- * library without restriction.  Specifically, if other files instantiate
- * templates or use macros or inline functions from this file, or you compile
- * this file and link it with other files to produce an executable, this
- * file does not by itself cause the resulting executable to be covered by
- * the GNU General Public License.  This exception does not however
- * invalidate any other reasons why the executable file might be covered by
- * the GNU General Public License.
- */
-
-package processing.app.helpers;
-
-import org.fest.swing.core.Robot;
-import org.fest.swing.driver.JComponentDriver;
-import org.fest.swing.edt.GuiActionRunner;
-import org.fest.swing.edt.GuiQuery;
-import processing.app.syntax.SketchTextArea;
-
-public class SketchTextAreaComponentDriver extends JComponentDriver {
-
-  public SketchTextAreaComponentDriver(Robot robot) {
-    super(robot);
-  }
-
-  public void enterText(SketchTextArea target, String text) {
-    focusAndWaitForFocusGain(target);
-    robot.enterText(text);
-  }
-
-  public void setText(final SketchTextArea target, final String text) {
-    focusAndWaitForFocusGain(target);
-    GuiActionRunner.execute(new GuiQuery<SketchTextArea>() {
-
-      protected SketchTextArea executeInEDT() {
-        target.setText(text);
-        return target;
-      }
-
-    });
-    robot.waitForIdle();
-  }
-
-  public String getText(final SketchTextArea target) {
-    focusAndWaitForFocusGain(target);
-    return GuiActionRunner.execute(new GuiQuery<String>() {
-
-      protected String executeInEDT() {
-        return target.getText();
-      }
-
-    });
-  }
-
-  public SketchTextArea selectAll(final SketchTextArea target) {
-    return GuiActionRunner.execute(new GuiQuery<SketchTextArea>() {
-
-      protected SketchTextArea executeInEDT() {
-        target.selectAll();
-        return target;
-      }
-
-    });
-  }
-
-  public Integer getCaretPosition(final SketchTextArea target) {
-    focusAndWaitForFocusGain(target);
-    return GuiActionRunner.execute(new GuiQuery<Integer>() {
-
-      protected Integer executeInEDT() {
-        return target.getCaretPosition();
-      }
-
-    });
-  }
-
-  public void setCaretPosition(final SketchTextArea target, final int caretPosition) {
-    focusAndWaitForFocusGain(target);
-    GuiActionRunner.execute(new GuiQuery<SketchTextArea>() {
-
-      protected SketchTextArea executeInEDT() {
-        target.setCaretPosition(caretPosition);
-        return target;
-      }
-
-    });
-    robot.waitForIdle();
-  }
-
-}
diff --git a/app/test/processing/app/helpers/SketchTextAreaComponentMatcher.java b/app/test/processing/app/helpers/SketchTextAreaComponentMatcher.java
deleted file mode 100644
index 006ec2a91fc..00000000000
--- a/app/test/processing/app/helpers/SketchTextAreaComponentMatcher.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * This file is part of Arduino.
- *
- * Copyright 2015 Ricardo JL Rufino (ricardo@criativasoft.com.br)
- * Copyright 2015 Arduino LLC
- *
- * Arduino is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
- *
- * As a special exception, you may use this file as part of a free software
- * library without restriction.  Specifically, if other files instantiate
- * templates or use macros or inline functions from this file, or you compile
- * this file and link it with other files to produce an executable, this
- * file does not by itself cause the resulting executable to be covered by
- * the GNU General Public License.  This exception does not however
- * invalidate any other reasons why the executable file might be covered by
- * the GNU General Public License.
- */
-
-package processing.app.helpers;
-
-import org.fest.swing.core.ComponentMatcher;
-import processing.app.syntax.SketchTextArea;
-
-import java.awt.*;
-
-public class SketchTextAreaComponentMatcher implements ComponentMatcher {
-
-  private final String name;
-
-  public SketchTextAreaComponentMatcher(String name) {
-    this.name = name;
-  }
-
-  @Override
-  public boolean matches(Component component) {
-    return component instanceof SketchTextArea && name.equals(component.getName());
-  }
-}
diff --git a/app/test/processing/app/helpers/SketchTextAreaFixture.java b/app/test/processing/app/helpers/SketchTextAreaFixture.java
index e5e1f703b69..871dc3d7abb 100644
--- a/app/test/processing/app/helpers/SketchTextAreaFixture.java
+++ b/app/test/processing/app/helpers/SketchTextAreaFixture.java
@@ -30,53 +30,79 @@
 
 package processing.app.helpers;
 
-import org.fest.swing.core.Robot;
-import org.fest.swing.fixture.ComponentFixture;
-import processing.app.syntax.SketchTextArea;
-
-public class SketchTextAreaFixture extends ComponentFixture {
-
-  private final SketchTextAreaComponentDriver driver;
+import org.assertj.swing.core.Robot;
+import org.assertj.swing.driver.JComponentDriver;
+import org.assertj.swing.edt.GuiActionRunner;
+import org.assertj.swing.edt.GuiQuery;
 
-  public SketchTextAreaFixture(Robot robot, Class type) {
-    super(robot, type);
-    this.driver = new SketchTextAreaComponentDriver(robot);
-  }
+import processing.app.syntax.SketchTextArea;
 
-  public SketchTextAreaFixture(Robot robot, String name, Class type) {
-    super(robot, name, type);
-    this.driver = new SketchTextAreaComponentDriver(robot);
-  }
+public class SketchTextAreaFixture {
+  private final JComponentDriver driver;
+  private final Robot robot;
+  private final SketchTextArea target;
 
   public SketchTextAreaFixture(Robot robot, SketchTextArea target) {
-    super(robot, target);
-    this.driver = new SketchTextAreaComponentDriver(robot);
+    this.robot = robot;
+    this.target = target;
+    this.driver = new JComponentDriver(robot);
   }
 
-  public SketchTextAreaFixture enterText(String text) {
-    driver.enterText((SketchTextArea) target, text);
-    return this;
+  public void enterText(String text) {
+    driver.focusAndWaitForFocusGain(target);
+    robot.enterText(text);
   }
 
-  public SketchTextAreaFixture setText(String text) {
-    driver.setText((SketchTextArea) target, text);
-    return this;
+  public void setText(String text) {
+    driver.focusAndWaitForFocusGain(target);
+    GuiActionRunner.execute(new GuiQuery<SketchTextArea>() {
+      protected SketchTextArea executeInEDT() {
+        target.setText(text);
+        return target;
+      }
+    });
+    robot.waitForIdle();
   }
 
   public String getText() {
-    return driver.getText((SketchTextArea) target);
+    driver.focusAndWaitForFocusGain(target);
+    String text = GuiActionRunner.execute(new GuiQuery<String>() {
+      protected String executeInEDT() {
+        return target.getText();
+      }
+    });
+    robot.waitForIdle();
+    return text;
   }
 
-  public SketchTextAreaFixture selectAll() {
-    driver.selectAll((SketchTextArea) target);
-    return this;
+  public void selectAll() {
+    GuiActionRunner.execute(new GuiQuery<SketchTextArea>() {
+      protected SketchTextArea executeInEDT() {
+        target.selectAll();
+        return target;
+      }
+    });
   }
 
   public int getCaretPosition() {
-    return driver.getCaretPosition((SketchTextArea) target);
+    driver.focusAndWaitForFocusGain(target);
+    int caretPos = GuiActionRunner.execute(new GuiQuery<Integer>() {
+      protected Integer executeInEDT() {
+        return target.getCaretPosition();
+      }
+    });
+    robot.waitForIdle();
+    return caretPos;
   }
 
   public void setCaretPosition(int caretPosition) {
-    driver.setCaretPosition((SketchTextArea) target, caretPosition);
+    driver.focusAndWaitForFocusGain(target);
+    GuiActionRunner.execute(new GuiQuery<SketchTextArea>() {
+      protected SketchTextArea executeInEDT() {
+        target.setCaretPosition(caretPosition);
+        return target;
+      }
+    });
+    robot.waitForIdle();
   }
 }
diff --git a/arduino-core/src/processing/app/BaseNoGui.java b/arduino-core/src/processing/app/BaseNoGui.java
index c47a82d69b8..ed0ae992c25 100644
--- a/arduino-core/src/processing/app/BaseNoGui.java
+++ b/arduino-core/src/processing/app/BaseNoGui.java
@@ -294,7 +294,6 @@ static public File getSettingsFolder() {
     String preferencesPath = PreferencesData.get("settings.path");
     if (preferencesPath != null) {
       settingsFolder = absoluteFile(preferencesPath);
-
     } else {
       try {
         settingsFolder = getPlatform().getSettingsFolder();
@@ -305,7 +304,7 @@ static public File getSettingsFolder() {
     }
 
     // create the folder if it doesn't exist already
-    if (!settingsFolder.exists()) {
+    if (settingsFolder != null && !settingsFolder.exists()) {
       if (!settingsFolder.mkdirs()) {
         showError(tr("Settings issues"),
                 tr("Arduino cannot run because it could not\n" +
diff --git a/build/build.xml b/build/build.xml
index 0e5f63e09e1..83803f0dbdb 100644
--- a/build/build.xml
+++ b/build/build.xml
@@ -365,9 +365,11 @@
     <delete dir="macosx/work" />
     <delete dir="macosx/working_dir" />
     <delete dir="macosx/working.dmg" />
-    <delete file="macosx/arduino-*.dmg" />
     <delete>
-      <fileset dir="macosx" includes="arduino-*macosx*.zip"/>
+      <fileset dir="macosx" includes="arduino-*.dmg" />
+    </delete>
+    <delete>
+      <fileset dir="macosx" includes="arduino-*macosx*.zip" />
     </delete>
   </target>
 
@@ -1037,10 +1039,6 @@
     <fixcrlf file="windows/work/revisions.txt" eol="dos"/>
     <fixcrlf file="windows/work/lib/formatter.conf" eol="dos"/>
 
-    <copy todir="windows/work">
-      <fileset dir="windows/dist" includes="*.dll" />
-    </copy>
-
     <copy todir="windows/work">
       <fileset dir="windows/dist" includes="drivers/**" />
     </copy>
@@ -1054,8 +1052,6 @@
       <param name="dest_folder" value="${staging_folder}" />
     </antcall>
     <copy file="windows/libastylej-2.05.1/AStylej.dll" todir="windows/work/lib" />
-    <copy file="windows/msvcp100.dll" todir="windows/work" />
-    <copy file="windows/msvcr100.dll" todir="windows/work" />
 
     <antcall target="unzip">
       <param name="archive_file" value="./liblistSerials-${LIBLISTSERIAL-VERSION}.zip" />
@@ -1159,6 +1155,15 @@
       </condition>
     </fail>
 
+    <copy todir="windows/work">
+      <fileset dir="windows/dist" includes="*.dll" />
+    </copy>
+    <!-- Copy also inside java/bin so they can be found even if arduino.exe or
+         arduino_debug.exe is launched from another working directory -->
+    <copy todir="windows/work/java/bin">
+      <fileset dir="windows/dist" includes="*.dll" />
+    </copy>
+
     <copy todir="${staging_folder}/work/java" includeemptydirs="true" preservelastmodified="true" overwrite="true" failonerror="true">
       <fileset dir="${WINDOWS_BUNDLED_JVM}" includes="*/**"/>
     </copy>
diff --git a/build/windows/msvcp100.dll b/build/windows/dist/msvcp100.dll
similarity index 100%
rename from build/windows/msvcp100.dll
rename to build/windows/dist/msvcp100.dll
diff --git a/build/windows/msvcr100.dll b/build/windows/dist/msvcr100.dll
similarity index 100%
rename from build/windows/msvcr100.dll
rename to build/windows/dist/msvcr100.dll