From 9898fd7ae6b5663f16daed7c74f7e6279c55ede1 Mon Sep 17 00:00:00 2001 From: Federico Fissore Date: Thu, 10 Oct 2013 15:52:35 +0200 Subject: [PATCH] Added support to openssh config file --- .../cc/arduino/packages/DiscoveryManager.java | 12 ++- .../packages/UploaderAndMonitorFactory.java | 13 ++- .../discoverers/NetworkDiscovery.java | 2 +- .../packages/ssh/NoInteractionUserInfo.java | 36 +++++++ .../packages/{uploaders => }/ssh/SCP.java | 3 +- .../packages/{uploaders => }/ssh/SSH.java | 2 +- .../packages/ssh/SSHClientSetupChainRing.java | 17 ++++ .../packages/ssh/SSHConfigFileSetup.java | 93 +++++++++++++++++++ .../cc/arduino/packages/ssh/SSHPwdSetup.java | 21 +++++ .../packages/uploaders/SSHUploader.java | 29 +++--- app/src/processing/app/Constants.java | 9 -- app/src/processing/app/Editor.java | 43 ++++++--- app/src/processing/app/NetworkMonitor.java | 49 +++------- app/src/processing/app/SerialMonitor.java | 7 +- app/src/processing/app/Sketch.java | 5 +- .../app/debug/UploaderFactoryTest.java | 19 +++- 16 files changed, 266 insertions(+), 94 deletions(-) create mode 100644 app/src/cc/arduino/packages/ssh/NoInteractionUserInfo.java rename app/src/cc/arduino/packages/{uploaders => }/ssh/SCP.java (97%) rename app/src/cc/arduino/packages/{uploaders => }/ssh/SSH.java (98%) create mode 100644 app/src/cc/arduino/packages/ssh/SSHClientSetupChainRing.java create mode 100644 app/src/cc/arduino/packages/ssh/SSHConfigFileSetup.java create mode 100644 app/src/cc/arduino/packages/ssh/SSHPwdSetup.java delete mode 100644 app/src/processing/app/Constants.java diff --git a/app/src/cc/arduino/packages/DiscoveryManager.java b/app/src/cc/arduino/packages/DiscoveryManager.java index dbf7ccc97..5c45314a1 100644 --- a/app/src/cc/arduino/packages/DiscoveryManager.java +++ b/app/src/cc/arduino/packages/DiscoveryManager.java @@ -73,9 +73,19 @@ public class DiscoveryManager { public List discovery() { List res = new ArrayList(); - for (Discovery d : discoverers) + for (Discovery d : discoverers) { res.addAll(d.discovery()); + } return res; } + public BoardPort find(String address) { + for (BoardPort boardPort : discovery()) { + if (boardPort.getAddress().equals(address)) { + return boardPort; + } + } + return null; + } + } diff --git a/app/src/cc/arduino/packages/UploaderAndMonitorFactory.java b/app/src/cc/arduino/packages/UploaderAndMonitorFactory.java index 0dc38a889..30761ed78 100644 --- a/app/src/cc/arduino/packages/UploaderAndMonitorFactory.java +++ b/app/src/cc/arduino/packages/UploaderAndMonitorFactory.java @@ -31,21 +31,24 @@ package cc.arduino.packages; import cc.arduino.packages.uploaders.SSHUploader; import cc.arduino.packages.uploaders.SerialUploader; -import processing.app.*; +import processing.app.AbstractMonitor; +import processing.app.Base; +import processing.app.NetworkMonitor; +import processing.app.SerialMonitor; import processing.app.debug.TargetBoard; public class UploaderAndMonitorFactory { - public Uploader newUploader(TargetBoard board, String port) { - if ("true".equals(board.getPreferences().get("upload.via_ssh")) && Constants.IPV4_ADDRESS.matcher(port).find()) { + public Uploader newUploader(TargetBoard board, BoardPort port) { + if ("true".equals(board.getPreferences().get("upload.via_ssh")) && "network".equals(port.getProtocol())) { return new SSHUploader(port); } return new SerialUploader(); } - public AbstractMonitor newMonitor(String port, Base base) { - if (Constants.IPV4_ADDRESS.matcher(port).find()) { + public AbstractMonitor newMonitor(BoardPort port, Base base) { + if ("network".equals(port.getProtocol())) { return new NetworkMonitor(port, base); } diff --git a/app/src/cc/arduino/packages/discoverers/NetworkDiscovery.java b/app/src/cc/arduino/packages/discoverers/NetworkDiscovery.java index a1613bda2..835d93158 100644 --- a/app/src/cc/arduino/packages/discoverers/NetworkDiscovery.java +++ b/app/src/cc/arduino/packages/discoverers/NetworkDiscovery.java @@ -31,7 +31,7 @@ package cc.arduino.packages.discoverers; import cc.arduino.packages.BoardPort; import cc.arduino.packages.Discovery; -import cc.arduino.packages.discoverers.network.NetworkChecker; +import cc.arduino.packages.discoverers.network.*; import processing.app.Base; import processing.app.helpers.NetUtils; import processing.app.helpers.PreferencesMap; diff --git a/app/src/cc/arduino/packages/ssh/NoInteractionUserInfo.java b/app/src/cc/arduino/packages/ssh/NoInteractionUserInfo.java new file mode 100644 index 000000000..a689dc957 --- /dev/null +++ b/app/src/cc/arduino/packages/ssh/NoInteractionUserInfo.java @@ -0,0 +1,36 @@ +package cc.arduino.packages.ssh; + +import com.jcraft.jsch.UserInfo; + +public class NoInteractionUserInfo implements UserInfo { + + private final String password; + + public NoInteractionUserInfo(String password) { + this.password = password; + } + + public String getPassword() { + return password; + } + + public boolean promptYesNo(String str) { + return true; + } + + public String getPassphrase() { + return password; + } + + public boolean promptPassphrase(String message) { + return true; + } + + public boolean promptPassword(String message) { + return true; + } + + public void showMessage(String message) { + } + +} diff --git a/app/src/cc/arduino/packages/uploaders/ssh/SCP.java b/app/src/cc/arduino/packages/ssh/SCP.java similarity index 97% rename from app/src/cc/arduino/packages/uploaders/ssh/SCP.java rename to app/src/cc/arduino/packages/ssh/SCP.java index 7647cbd94..458e1b8d5 100644 --- a/app/src/cc/arduino/packages/uploaders/ssh/SCP.java +++ b/app/src/cc/arduino/packages/ssh/SCP.java @@ -27,11 +27,10 @@ * Copyright 2013 Arduino LLC (http://www.arduino.cc/) */ -package cc.arduino.packages.uploaders.ssh; +package cc.arduino.packages.ssh; import com.jcraft.jsch.Channel; import com.jcraft.jsch.ChannelExec; -import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import java.io.*; diff --git a/app/src/cc/arduino/packages/uploaders/ssh/SSH.java b/app/src/cc/arduino/packages/ssh/SSH.java similarity index 98% rename from app/src/cc/arduino/packages/uploaders/ssh/SSH.java rename to app/src/cc/arduino/packages/ssh/SSH.java index 4e4372c1a..0bb6f8a34 100644 --- a/app/src/cc/arduino/packages/uploaders/ssh/SSH.java +++ b/app/src/cc/arduino/packages/ssh/SSH.java @@ -27,7 +27,7 @@ * Copyright 2013 Arduino LLC (http://www.arduino.cc/) */ -package cc.arduino.packages.uploaders.ssh; +package cc.arduino.packages.ssh; import com.jcraft.jsch.Channel; import com.jcraft.jsch.ChannelExec; diff --git a/app/src/cc/arduino/packages/ssh/SSHClientSetupChainRing.java b/app/src/cc/arduino/packages/ssh/SSHClientSetupChainRing.java new file mode 100644 index 000000000..7590e8427 --- /dev/null +++ b/app/src/cc/arduino/packages/ssh/SSHClientSetupChainRing.java @@ -0,0 +1,17 @@ +package cc.arduino.packages.ssh; + +import cc.arduino.packages.BoardPort; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; + +import java.io.IOException; + +public interface SSHClientSetupChainRing { + + /* + Chain is actually useless as default JSCH behaviour is to follow SSH Server authentication methods list + */ + Session setup(BoardPort port, JSch jSch) throws JSchException, IOException; + +} diff --git a/app/src/cc/arduino/packages/ssh/SSHConfigFileSetup.java b/app/src/cc/arduino/packages/ssh/SSHConfigFileSetup.java new file mode 100644 index 000000000..c3432a023 --- /dev/null +++ b/app/src/cc/arduino/packages/ssh/SSHConfigFileSetup.java @@ -0,0 +1,93 @@ +package cc.arduino.packages.ssh; + +import cc.arduino.packages.BoardPort; +import com.jcraft.jsch.*; + +import java.io.File; +import java.io.IOException; + +public class SSHConfigFileSetup implements SSHClientSetupChainRing { + + private final SSHClientSetupChainRing nextChainRing; + + public SSHConfigFileSetup(SSHClientSetupChainRing nextChainRing) { + this.nextChainRing = nextChainRing; + } + + public Session setup(BoardPort port, JSch jSch) throws JSchException, IOException { + String ipAddress = port.getAddress(); + String hostname = port.getBoardName().contains(".local") ? port.getBoardName() : port.getBoardName() + ".local"; + + File sshFolder = new File(System.getProperty("user.home"), ".ssh"); + File sshConfig = new File(sshFolder, "config"); + + if (!sshFolder.exists() || !sshConfig.exists()) { + if (nextChainRing != null) { + return nextChainRing.setup(port, jSch); + } + throw new JSchException("Unable to find a way to connect"); + } + + OpenSSHConfig configRepository = OpenSSHConfig.parseFile(sshConfig.getAbsolutePath()); + + jSch.setConfigRepository(new OpenSSHConfigWrapper(configRepository, ipAddress)); + + return jSch.getSession(hostname); + } + + public static class OpenSSHConfigWrapper implements ConfigRepository { + + private final OpenSSHConfig config; + private final String ipAddress; + + public OpenSSHConfigWrapper(OpenSSHConfig config, String ipAddress) { + this.config = config; + this.ipAddress = ipAddress; + } + + @Override + public Config getConfig(String host) { + return new ConfigWrapper(config.getConfig(host), ipAddress); + } + } + + public static class ConfigWrapper implements ConfigRepository.Config { + + private final ConfigRepository.Config config; + private final String ipAddress; + + public ConfigWrapper(OpenSSHConfig.Config config, String ipAddress) { + this.config = config; + this.ipAddress = ipAddress; + } + + @Override + public String getHostname() { + return ipAddress; + } + + @Override + public String getUser() { + String user = config.getUser(); + if (user != null) { + return user; + } + return "root"; + } + + @Override + public int getPort() { + return config.getPort(); + } + + @Override + public String getValue(String key) { + return config.getValue(key); + } + + @Override + public String[] getValues(String key) { + return config.getValues(key); + } + } +} diff --git a/app/src/cc/arduino/packages/ssh/SSHPwdSetup.java b/app/src/cc/arduino/packages/ssh/SSHPwdSetup.java new file mode 100644 index 000000000..ad031541a --- /dev/null +++ b/app/src/cc/arduino/packages/ssh/SSHPwdSetup.java @@ -0,0 +1,21 @@ +package cc.arduino.packages.ssh; + +import cc.arduino.packages.BoardPort; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import processing.app.Preferences; + +public class SSHPwdSetup implements SSHClientSetupChainRing { + + @Override + public Session setup(BoardPort port, JSch jSch) throws JSchException { + String ipAddress = port.getAddress(); + + Session session = jSch.getSession("root", ipAddress, 22); + session.setPassword(Preferences.get("runtime.pwd." + ipAddress)); + + return session; + } + +} diff --git a/app/src/cc/arduino/packages/uploaders/SSHUploader.java b/app/src/cc/arduino/packages/uploaders/SSHUploader.java index 1ea853fa7..3358501e7 100644 --- a/app/src/cc/arduino/packages/uploaders/SSHUploader.java +++ b/app/src/cc/arduino/packages/uploaders/SSHUploader.java @@ -29,15 +29,13 @@ package cc.arduino.packages.uploaders; +import cc.arduino.packages.BoardPort; import cc.arduino.packages.Uploader; -import cc.arduino.packages.uploaders.ssh.SCP; -import cc.arduino.packages.uploaders.ssh.SSH; +import cc.arduino.packages.ssh.*; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import processing.app.Base; -import processing.app.Constants; -import processing.app.NetworkMonitor; import processing.app.Preferences; import processing.app.debug.RunnerException; import processing.app.debug.TargetPlatform; @@ -48,7 +46,6 @@ import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.List; -import java.util.regex.Matcher; import static processing.app.I18n._; @@ -56,22 +53,19 @@ public class SSHUploader extends Uploader { private static final List FILES_NOT_TO_COPY = Arrays.asList(".DS_Store", ".Trash", "Thumbs.db", "__MACOSX"); - private final String ipAddress; + private final BoardPort port; - public SSHUploader(String port) { - Matcher matcher = Constants.IPV4_ADDRESS.matcher(port); - if (!matcher.find()) { - throw new IllegalArgumentException(port); - } - ipAddress = matcher.group(); + public SSHUploader(BoardPort port) { + this.port = port; } public boolean requiresAuthorization() { return true; } + @Override public String getAuthorizationKey() { - return "runtime.pwd." + ipAddress; + return "runtime.pwd." + port.getAddress(); } @Override @@ -84,10 +78,10 @@ public class SSHUploader extends Uploader { SCP scp = null; try { JSch jSch = new JSch(); - session = jSch.getSession("root", ipAddress, 22); - session.setPassword(Preferences.get(getAuthorizationKey())); + SSHClientSetupChainRing sshClientSetupChain = new SSHConfigFileSetup(new SSHPwdSetup()); + session = sshClientSetupChain.setup(port, jSch); - session.setUserInfo(new NetworkMonitor.NoInteractionUserInfo()); + session.setUserInfo(new NoInteractionUserInfo(Preferences.get("runtime.pwd." + port.getAddress()))); session.connect(30000); scp = new SCP(session); @@ -97,7 +91,8 @@ public class SSHUploader extends Uploader { return runAVRDude(ssh); } catch (JSchException e) { - if ("Auth cancel".equals(e.getMessage())) { + String message = e.getMessage(); + if ("Auth cancel".equals(message) || "Auth fail".equals(message)) { return false; } throw new RunnerException(e); diff --git a/app/src/processing/app/Constants.java b/app/src/processing/app/Constants.java deleted file mode 100644 index b4159cd0c..000000000 --- a/app/src/processing/app/Constants.java +++ /dev/null @@ -1,9 +0,0 @@ -package processing.app; - -import java.util.regex.Pattern; - -public class Constants { - - public static final Pattern IPV4_ADDRESS = Pattern.compile("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"); - -} diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index 0929092f7..29cf16fda 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -209,11 +209,6 @@ public class Editor extends JFrame implements RunnerListener { //PdeKeywords keywords = new PdeKeywords(); //sketchbook = new Sketchbook(this); - if (serialMonitor == null) { - serialMonitor = new UploaderAndMonitorFactory().newMonitor(Preferences.get("serial.port"), base); - serialMonitor.setIconImage(getIconImage()); - } - buildMenuBar(); // For rev 0120, placing things inside a JPanel @@ -971,13 +966,14 @@ public class Editor extends JFrame implements RunnerListener { Preferences.set("serial.port.file", name.substring(5)); else Preferences.set("serial.port.file", name); - try { - serialMonitor.close(); - } catch (Exception e) { - // ignore + if (serialMonitor != null) { + try { + serialMonitor.close(); + serialMonitor.setVisible(false); + } catch (Exception e) { + // ignore + } } - serialMonitor.setVisible(false); - serialMonitor = new UploaderAndMonitorFactory().newMonitor(Preferences.get("serial.port"), base); onBoardOrPortChange(); @@ -2389,8 +2385,10 @@ public class Editor extends JFrame implements RunnerListener { public void run() { try { - serialMonitor.close(); - serialMonitor.setVisible(false); + if (serialMonitor != null) { + serialMonitor.close(); + serialMonitor.setVisible(false); + } uploading = true; @@ -2429,8 +2427,10 @@ public class Editor extends JFrame implements RunnerListener { public void run() { try { - serialMonitor.close(); - serialMonitor.setVisible(false); + if (serialMonitor != null) { + serialMonitor.close(); + serialMonitor.setVisible(false); + } uploading = true; @@ -2502,6 +2502,19 @@ public class Editor extends JFrame implements RunnerListener { public void handleSerial() { if (uploading) return; + if (serialMonitor != null) { + try { + serialMonitor.close(); + serialMonitor.setVisible(false); + } catch (Exception e) { + // noop + } + } + + BoardPort port = Base.getDiscoveryManager().find(Preferences.get("serial.port")); + serialMonitor = new UploaderAndMonitorFactory().newMonitor(port, base); + serialMonitor.setIconImage(getIconImage()); + boolean success = false; do { if (serialMonitor.requiresAuthorization() && !Preferences.has(serialMonitor.getAuthorizationKey())) { diff --git a/app/src/processing/app/NetworkMonitor.java b/app/src/processing/app/NetworkMonitor.java index f364eb19d..73a973aad 100644 --- a/app/src/processing/app/NetworkMonitor.java +++ b/app/src/processing/app/NetworkMonitor.java @@ -1,5 +1,10 @@ package processing.app; +import cc.arduino.packages.BoardPort; +import cc.arduino.packages.ssh.NoInteractionUserInfo; +import cc.arduino.packages.ssh.SSHClientSetupChainRing; +import cc.arduino.packages.ssh.SSHConfigFileSetup; +import cc.arduino.packages.ssh.SSHPwdSetup; import com.jcraft.jsch.*; import processing.app.debug.MessageSiphon; @@ -9,7 +14,6 @@ import java.awt.event.ActionListener; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.regex.Matcher; import static processing.app.I18n._; @@ -18,6 +22,7 @@ public class NetworkMonitor extends AbstractMonitor { private static final int MAX_CONNECTION_ATTEMPTS = 5; + private final BoardPort port; private final String ipAddress; private MessageSiphon inputConsumer; @@ -26,12 +31,10 @@ public class NetworkMonitor extends AbstractMonitor { private MessageSiphon errorConsumer; private int connectionAttempts; - public NetworkMonitor(String port, Base base) { - super(port); - - Matcher matcher = Constants.IPV4_ADDRESS.matcher(port); - matcher.find(); - this.ipAddress = matcher.group(); + public NetworkMonitor(BoardPort port, Base base) { + super(port.getLabel()); + this.port = port; + this.ipAddress = port.getAddress(); onSendCommand(new ActionListener() { public void actionPerformed(ActionEvent event) { @@ -63,10 +66,10 @@ public class NetworkMonitor extends AbstractMonitor { this.connectionAttempts = 0; JSch jSch = new JSch(); - session = jSch.getSession("root", ipAddress, 22); - session.setPassword(Preferences.get(getAuthorizationKey())); + SSHClientSetupChainRing sshClientSetupChain = new SSHConfigFileSetup(new SSHPwdSetup()); + session = sshClientSetupChain.setup(port, jSch); - session.setUserInfo(new NoInteractionUserInfo()); + session.setUserInfo(new NoInteractionUserInfo(Preferences.get(getAuthorizationKey()))); session.connect(30000); tryConnect(); @@ -161,30 +164,4 @@ public class NetworkMonitor extends AbstractMonitor { } } - public static class NoInteractionUserInfo implements UserInfo { - - public String getPassword() { - return null; - } - - public boolean promptYesNo(String str) { - return true; - } - - public String getPassphrase() { - return null; - } - - public boolean promptPassphrase(String message) { - return false; - } - - public boolean promptPassword(String message) { - return false; - } - - public void showMessage(String message) { - } - - } } diff --git a/app/src/processing/app/SerialMonitor.java b/app/src/processing/app/SerialMonitor.java index 1c139a887..49e7006a3 100644 --- a/app/src/processing/app/SerialMonitor.java +++ b/app/src/processing/app/SerialMonitor.java @@ -18,6 +18,7 @@ package processing.app; +import cc.arduino.packages.BoardPort; import processing.core.PApplet; import java.awt.*; @@ -32,10 +33,10 @@ public class SerialMonitor extends AbstractMonitor { private Serial serial; private int serialRate; - public SerialMonitor(String port) { - super(port); + public SerialMonitor(BoardPort port) { + super(port.getLabel()); - this.port = port; + this.port = port.getAddress(); serialRate = Preferences.getInteger("serial.debug_rate"); serialRates.setSelectedItem(serialRate + " " + _("baud")); diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index 7ddf6760d..f18e4691d 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -23,6 +23,7 @@ package processing.app; +import cc.arduino.packages.BoardPort; import cc.arduino.packages.UploaderAndMonitorFactory; import cc.arduino.packages.Uploader; @@ -1667,7 +1668,9 @@ public class Sketch { TargetPlatform target = Base.getTargetPlatform(); String board = Preferences.get("board"); - Uploader uploader = new UploaderAndMonitorFactory().newUploader(target.getBoards().get(board), Preferences.get("serial.port")); + BoardPort boardPort = Base.getDiscoveryManager().find(Preferences.get("serial.port")); + + Uploader uploader = new UploaderAndMonitorFactory().newUploader(target.getBoards().get(board), boardPort); boolean success = false; do { diff --git a/app/test/processing/app/debug/UploaderFactoryTest.java b/app/test/processing/app/debug/UploaderFactoryTest.java index b5094aa27..a785f835e 100644 --- a/app/test/processing/app/debug/UploaderFactoryTest.java +++ b/app/test/processing/app/debug/UploaderFactoryTest.java @@ -1,5 +1,6 @@ package processing.app.debug; +import cc.arduino.packages.BoardPort; import cc.arduino.packages.Uploader; import cc.arduino.packages.UploaderAndMonitorFactory; import cc.arduino.packages.uploaders.SSHUploader; @@ -24,7 +25,11 @@ public class UploaderFactoryTest extends AbstractWithPreferencesTest { @Test public void shouldCreateAnInstanceOfSSHUploader() throws Exception { TargetBoard board = targetPackage.getPlatforms().get("avr").getBoards().get("yun"); - Uploader uploader = new UploaderAndMonitorFactory().newUploader(board, "192.168.0.1 (yun)"); + BoardPort boardPort = new BoardPort(); + boardPort.setBoardName("yun"); + boardPort.setAddress("192.168.0.1"); + boardPort.setProtocol("network"); + Uploader uploader = new UploaderAndMonitorFactory().newUploader(board, boardPort); assertTrue(uploader instanceof SSHUploader); } @@ -32,7 +37,11 @@ public class UploaderFactoryTest extends AbstractWithPreferencesTest { @Test public void shouldCreateAnInstanceOfBasicUploaderWhenSSHIsUnsupported() throws Exception { TargetBoard board = targetPackage.getPlatforms().get("avr").getBoards().get("uno"); - Uploader uploader = new UploaderAndMonitorFactory().newUploader(board, "192.168.0.1 (myyun)"); + BoardPort boardPort = new BoardPort(); + boardPort.setBoardName("myyun"); + boardPort.setAddress("192.168.0.1"); + boardPort.setProtocol("network"); + Uploader uploader = new UploaderAndMonitorFactory().newUploader(board, boardPort); assertTrue(uploader instanceof SerialUploader); } @@ -40,7 +49,11 @@ public class UploaderFactoryTest extends AbstractWithPreferencesTest { @Test public void shouldCreateAnInstanceOfBasicUploaderWhenPortIsSerial() throws Exception { TargetBoard board = targetPackage.getPlatforms().get("avr").getBoards().get("uno"); - Uploader uploader = new UploaderAndMonitorFactory().newUploader(board, "/dev/ttyACM0 (Arduino Leonardo)"); + BoardPort boardPort = new BoardPort(); + boardPort.setBoardName("Arduino Leonardo"); + boardPort.setAddress("/dev/ttyACM0"); + boardPort.setProtocol("serial"); + Uploader uploader = new UploaderAndMonitorFactory().newUploader(board, boardPort); assertTrue(uploader instanceof SerialUploader); }