Skip to content
Permalink
Browse files

[JENKINS-50378] Add an optional flag that allows to force the install…

…ation of a 32bit package of the NodeJS. The flag is allowed only in case a NodeJS package is available for the underlying system and 32bit architecture otherwise the build will fail.
  • Loading branch information...
nfalco79 committed Mar 31, 2018
1 parent 5aecce6 commit 14e5121888d842e5b9679b2f7ae08ddce5cc1588
@@ -42,7 +42,7 @@
</licenses>

<properties>
<powermock.version>1.7.0</powermock.version>
<powermock.version>1.7.3</powermock.version>
<jenkins.version>1.651.3</jenkins.version>
</properties>

@@ -20,6 +20,7 @@
import hudson.remoting.VirtualChannel;
import hudson.util.StreamTaskListener;
import jenkins.MasterToSlaveFileCallable;
import jenkins.plugins.nodejs.Messages;

/**
* CPU type.
@@ -32,16 +33,20 @@
*
* @param node
* the computer node
* @return a CPU value of the cpu of the given node
* @throws IOException in case of IO issues with the remote Node
* @throws InterruptedException in case the job is interrupted by user
* @return a CPU value of the architecture of the given node
* @throws DetectionFailedException
* when the current CPU node is not supported.
*/
public static CPU of(@Nonnull Node node) throws IOException, InterruptedException {
Computer computer = node.toComputer();
if (computer == null) {
throw new DetectionFailedException("Node offline");
public static CPU of(@Nonnull Node node) throws DetectionFailedException {
try {
Computer computer = node.toComputer();
if (computer == null) {
throw new DetectionFailedException(Messages.SystemTools_nodeNotAvailable(node.getDisplayName()));
}
return detect(computer, computer.getSystemProperties());
} catch (IOException | InterruptedException e) {
throw new DetectionFailedException(Messages.SystemTools_failureOnProperties(), e);
}
return detect(computer, computer.getSystemProperties());
}

/**
@@ -69,7 +74,7 @@ private static CPU detect(@Nullable Computer computer, Map<Object, Object> syste
FilePath rootPath = new FilePath((computer != null ? computer.getChannel() : null), "/");
arch = rootPath.act(new ArchitectureCallable());
} catch (IOException | InterruptedException e) {
throw new DetectionFailedException("Unknown CPU architecture: " + arch, e);
throw new DetectionFailedException(Messages.CPU_unknown(arch), e);
}
switch (arch) {
case "armv7l":
@@ -80,7 +85,7 @@ private static CPU detect(@Nullable Computer computer, Map<Object, Object> syste
return arm64;
}
}
throw new DetectionFailedException("Unknown CPU architecture: " + arch);
throw new DetectionFailedException(Messages.CPU_unknown(arch));
}

/**
@@ -41,6 +41,7 @@

import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;

import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
@@ -88,18 +89,24 @@
private final Long npmPackagesRefreshHours;
private Platform platform;
private CPU cpu;
private boolean force32Bit;

@DataBoundConstructor
public NodeJSInstaller(String id, String npmPackages, long npmPackagesRefreshHours) {
public NodeJSInstaller(String id, String npmPackages, long npmPackagesRefreshHours) {
super(id);
this.npmPackages = Util.fixEmptyAndTrim(npmPackages);
this.npmPackagesRefreshHours = npmPackagesRefreshHours;
}

public NodeJSInstaller(String id, String npmPackages, long npmPackagesRefreshHours, boolean force32bit) {
this(id, npmPackages, npmPackagesRefreshHours);
this.force32Bit = force32bit;
}

@Override
public Installable getInstallable() throws IOException {
Installable installable = super.getInstallable();
if(installable==null) {
if (installable == null) {
return null;
}

@@ -118,8 +125,8 @@ public Installable getInstallable() throws IOException {
// implementation
@Override
public FilePath performInstallation(ToolInstallation tool, Node node, TaskListener log) throws IOException, InterruptedException {
this.platform = getPlatform(node);
this.cpu = getCPU(node);
this.platform = ToolsUtils.getPlatform(node);
this.cpu = ToolsUtils.getCPU(node, force32Bit);

FilePath expected;
Installable installable = getInstallable();
@@ -144,14 +151,6 @@ public FilePath performInstallation(ToolInstallation tool, Node node, TaskListen
return expected;
}

private CPU getCPU(Node node) throws IOException, InterruptedException {
return CPU.of(node);
}

private Platform getPlatform(Node node) throws DetectionFailedException {
return Platform.of(node);
}

/*
* Installing npm packages if needed
*/
@@ -332,6 +331,15 @@ public Long getNpmPackagesRefreshHours() {
return npmPackagesRefreshHours;
}

public boolean isForce32Bit() {
return force32Bit;
}

@DataBoundSetter
public void setForce32Bit(boolean force32Bit) {
this.force32Bit = force32Bit;
}

@Extension
public static final class DescriptorImpl extends DownloadFromUrlInstaller.DescriptorImpl<NodeJSInstaller> { // NOSONAR
@Override
@@ -6,6 +6,7 @@

import hudson.model.Computer;
import hudson.model.Node;
import jenkins.plugins.nodejs.Messages;

/**
* Supported platform.
@@ -49,11 +50,11 @@ public static Platform of(Node node) throws DetectionFailedException {
try {
Computer computer = node.toComputer();
if (computer == null) {
throw new DetectionFailedException("No executor available on Node " + node.getDisplayName());
throw new DetectionFailedException(Messages.SystemTools_nodeNotAvailable(node.getDisplayName()));
}
return detect(computer.getSystemProperties());
} catch (IOException | InterruptedException e) {
throw new DetectionFailedException("Error getting system properties on remote Node", e);
throw new DetectionFailedException(Messages.SystemTools_failureOnProperties(), e);
}
}

@@ -75,7 +76,7 @@ private static Platform detect(Map<Object, Object> systemProperties) throws Dete
if (arch.contains("sunos")) {
return SUNOS;
}
throw new DetectionFailedException("Unknown OS name: " + arch);
throw new DetectionFailedException(Messages.Platform_unknown(arch));
}

}
@@ -0,0 +1,47 @@
package jenkins.plugins.nodejs.tools;

import hudson.model.Node;
import jenkins.plugins.nodejs.Messages;

/*package */ class ToolsUtils {

private ToolsUtils() {
}

public static Platform getPlatform(Node node) throws DetectionFailedException {
return Platform.of(node);
}

public static CPU getCPU(Node node) throws DetectionFailedException {
return getCPU(node, false);
}

public static CPU getCPU(Node node, boolean force32bit) throws DetectionFailedException {
CPU nodeCPU = CPU.of(node);
if (force32bit) {
if (!support32Bit(nodeCPU)) {
throw new DetectionFailedException(Messages.SystemTools_unsupported32bitArchitecture());
}

// force 32 bit architecture
if (nodeCPU == CPU.amd64) {
nodeCPU = CPU.i386;
}
}
return nodeCPU;
}

private static boolean support32Bit(CPU cpu) {
switch (cpu) {
case armv6l:
// 64bit start with ARMv8
case armv7l:
case i386:
case amd64:
return true;
default:
return false;
}
}

}
@@ -37,4 +37,9 @@ NPMRegistry.DescriptorImpl.emptyRegistryURL=Registry URL is required
NPMRegistry.DescriptorImpl.invalidRegistryURL=Invalid URL, should start with https://
NPMRegistry.DescriptorImpl.emptyScopes=Scopes is required
NPMRegistry.DescriptorImpl.invalidScopes=Invalid scope
NPMRegistry.DescriptorImpl.invalidCharInScopes=Remove the '@' character from scope
NPMRegistry.DescriptorImpl.invalidCharInScopes=Remove the '@' character from scope
CPU.unknown=Unknown CPU architecture: {0}
Platform.unknown=Unknown OS name: {0}
SystemTools.nodeNotAvailable=Node could be offline or there are no executor defined for Node {0}
SystemTools.failureOnProperties=Error getting system properties on remote Node
SystemTools.unsupported32bitArchitecture=NodeJS does not have a 32bit package available for the current node
@@ -30,4 +30,16 @@ NodeJSCommandInterpreter.noInstallation=Nessuna installazione trovata in {0}. Pe
NodeJSCommandInterpreter.nodeOffline=Non posso prendere il node in quanto non \u00E8 online.
NPMConfig.displayName=Npm config file
NPMConfig.verifyTooGlobalRegistry=Ci sono troppi registry npm impostati come globali (cio\u00E8 senza uno scope assegnato).
NPMConfig.default=- usa il default di sistema -
NPMConfig.default=- usa il default di sistema -
NPMRegistry.DescriptorImpl.emptyCredentialsId=Le credenziali sono obbligatorie
NPMRegistry.DescriptorImpl.invalidCredentialsId=Le credenziali specificate non esistono
NPMRegistry.DescriptorImpl.emptyRegistryURL=L'URL del registry è un campo obbligatorio
NPMRegistry.DescriptorImpl.invalidRegistryURL=URL invalido, dovrebbe cominciare con https://
NPMRegistry.DescriptorImpl.emptyScopes=Scopes è un campo obbligatorio
NPMRegistry.DescriptorImpl.invalidScopes=Scope invalido
NPMRegistry.DescriptorImpl.invalidCharInScopes=Rimuovi il carattere '@' dallo scope
CPU.unknown=Architettura sconosciuta: {0}
Platform.unknown=Sistema operativo sconociuto: {0}
SystemTools.nodeNotAvailable=Node potrebbe essere offine oppure non ci sono executori definiti per il nodo {0}
SystemTools.failureOnProperties=Errore durante il recupero delle propietà di systema del nodo remoto
SystemTools.unsupported32bitArchitecture=Non ci sono pacchetti di NodeJS 32bit disponibili per il nodo corrente
@@ -40,6 +40,10 @@ THE SOFTWARE.
</j:choose>
</f:entry>

<f:entry title="${%force32Bit.title}" description="${%force32Bit.description}">
<f:checkbox field="force32Bit" />
</f:entry>

<f:entry title="${%npmPackages.title}" description="${%npmPackages.description}">
<f:textbox field="npmPackages" />
</f:entry>
@@ -24,4 +24,6 @@ id.title=Version
npmPackages.title=Global npm packages to install
npmPackages.description=Specify list of packages to install globally -- see npm install -g. Note that you can fix the package's version by using the syntax `packageName@version`
npmPackagesRefreshHours.title=Global npm packages refresh hours
npmPackagesRefreshHours.description=Duration, in hours, before 2 npm cache update. Note that 0 will always update npm cache
npmPackagesRefreshHours.description=Duration, in hours, before 2 npm cache update. Note that 0 will always update npm cache
force32Bit.title=Force 32bit architecture
force32Bit.description=For the underlying architecture, if available, force the installation of the 32bit package. Otherwise the build will fail
@@ -9,13 +9,13 @@
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import hudson.FilePath;
import hudson.model.Node;
import hudson.model.TaskListener;
import hudson.tools.DownloadFromUrlInstaller;
import hudson.tools.ToolInstallation;

@RunWith(PowerMockRunner.class)
@PrepareForTest(NodeJSInstaller.class)
public class NodeJSInstallerTest {

/**
@@ -29,26 +29,27 @@
*/
@Issue("JENKINS-41876")
@Test
@PrepareForTest({ NodeJSInstaller.class, ToolsUtils.class })
public void test_skip_install_global_packages_when_empty() throws Exception {
String expectedPackages = " ";
int expectedRefreshHours = NodeJSInstaller.DEFAULT_NPM_PACKAGES_REFRESH_HOURS;
Node currentNode = mock(Node.class);

// mock all the static methods in the class
PowerMockito.mockStatic(NodeJSInstaller.class);

// create partial mock
NodeJSInstaller installer = new NodeJSInstaller("test-id", expectedPackages, expectedRefreshHours);
NodeJSInstaller spy = PowerMockito.spy(installer);

// use Mockito to set up your expectation
when(NodeJSInstaller.areNpmPackagesUpToDate(null, expectedPackages, expectedRefreshHours)).thenThrow(new AssertionError());
PowerMockito.stub(PowerMockito.method(NodeJSInstaller.class, "areNpmPackagesUpToDate", FilePath.class, String.class, long.class)) //
.toThrow(new AssertionError("global package should skip install if is an empty string"));
PowerMockito.suppress(PowerMockito.methodsDeclaredIn(DownloadFromUrlInstaller.class));
PowerMockito.doReturn(null).when(spy).getInstallable();
PowerMockito.doReturn(Platform.LINUX).when(spy, "getPlatform", currentNode);
PowerMockito.doReturn(CPU.amd64).when(spy, "getCPU", currentNode);
when(spy.getNpmPackages()).thenReturn(expectedPackages);

PowerMockito.mockStatic(ToolsUtils.class);
when(ToolsUtils.getCPU(currentNode)).thenReturn(CPU.amd64);
when(ToolsUtils.getPlatform(currentNode)).thenReturn(Platform.LINUX);

// execute test
spy.performInstallation(mock(ToolInstallation.class), currentNode, mock(TaskListener.class));
}
@@ -0,0 +1,67 @@
package jenkins.plugins.nodejs.tools;

import static org.mockito.Mockito.*;

import org.hamcrest.CoreMatchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;

import hudson.model.Node;

@RunWith(PowerMockRunner.class)
@PrepareForTest(CPU.class)
public class ToolsUtilsTest {

@Before
public void setup() {
CPU[] cpuValues = CPU.values();
CPU mock = PowerMockito.mock(CPU.class);
for (CPU c : cpuValues) {
Whitebox.setInternalState(mock, "name", c.name());
Whitebox.setInternalState(mock, "ordinal", c.ordinal());
}

PowerMockito.mockStatic(CPU.class);
PowerMockito.when(CPU.values()).thenReturn(cpuValues);
}

@Test
public void nodejs_supports_32bit_64bit_on_windows_linux_mac() throws Exception {
Node currentNode = mock(Node.class);

when(CPU.of(currentNode)).thenReturn(CPU.amd64);
CPU cpu = ToolsUtils.getCPU(currentNode, true);
Assert.assertThat(cpu, CoreMatchers.is(CPU.i386));

cpu = ToolsUtils.getCPU(currentNode);
Assert.assertThat(cpu, CoreMatchers.is(CPU.amd64));
}

@Test(expected = DetectionFailedException.class)
public void nodejs_doesn_t_supports_32bit_on_armv64() throws Exception {
Node currentNode = mock(Node.class);

when(CPU.of(currentNode)).thenReturn(CPU.arm64);
ToolsUtils.getCPU(currentNode, true);
}

@Test
public void nodejs_supports_32bit_on_armv6_armv7() throws Exception {
Node currentNode = mock(Node.class);

when(CPU.of(currentNode)).thenReturn(CPU.armv7l);
CPU cpu = ToolsUtils.getCPU(currentNode, true);
Assert.assertThat(cpu, CoreMatchers.is(CPU.armv7l));

when(CPU.of(currentNode)).thenReturn(CPU.armv6l);
cpu = ToolsUtils.getCPU(currentNode, true);
Assert.assertThat(cpu, CoreMatchers.is(CPU.armv6l));
}

}

0 comments on commit 14e5121

Please sign in to comment.
You can’t perform that action at this time.