-
Notifications
You must be signed in to change notification settings - Fork 601
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Replace abstract class IntegrationBaseSpec with composition through IntegrationTestUtil * Switch to testcontainers in integration tests It allows running different SSH servers with different configurations in tests, giving ability to cover more bugs, like mentioned in #733.
- Loading branch information
1 parent
8a66dc5
commit d5805a6
Showing
14 changed files
with
280 additions
and
151 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
42 changes: 0 additions & 42 deletions
42
src/itest/groovy/com/hierynomus/sshj/IntegrationBaseSpec.groovy
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
21 changes: 21 additions & 0 deletions
21
src/itest/groovy/com/hierynomus/sshj/IntegrationTestUtil.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/* | ||
* Copyright (C)2009 - SSHJ Contributors | ||
* | ||
* 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. | ||
*/ | ||
package com.hierynomus.sshj | ||
|
||
class IntegrationTestUtil { | ||
static final String USERNAME = "sshj" | ||
static final String KEYFILE = "src/itest/resources/keyfiles/id_rsa" | ||
} |
77 changes: 77 additions & 0 deletions
77
src/itest/groovy/com/hierynomus/sshj/SshServerWaitStrategy.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
/* | ||
* Copyright (C)2009 - SSHJ Contributors | ||
* | ||
* 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. | ||
*/ | ||
package com.hierynomus.sshj; | ||
|
||
import org.testcontainers.containers.wait.strategy.WaitStrategy; | ||
import org.testcontainers.containers.wait.strategy.WaitStrategyTarget; | ||
|
||
import java.io.IOException; | ||
import java.net.InetSocketAddress; | ||
import java.net.Socket; | ||
import java.nio.charset.StandardCharsets; | ||
import java.time.Duration; | ||
import java.util.Arrays; | ||
|
||
/** | ||
* A wait strategy designed for {@link SshdContainer} to wait until the SSH server is ready, to avoid races when a test | ||
* tries to connect to a server before the server has started. | ||
*/ | ||
public class SshServerWaitStrategy implements WaitStrategy { | ||
private Duration startupTimeout = Duration.ofMinutes(1); | ||
|
||
@Override | ||
public void waitUntilReady(WaitStrategyTarget waitStrategyTarget) { | ||
long expectedEnd = System.nanoTime() + startupTimeout.toNanos(); | ||
while (true) { | ||
long attemptStart = System.nanoTime(); | ||
IOException error = null; | ||
byte[] buffer = new byte[7]; | ||
try (Socket socket = new Socket()) { | ||
socket.setSoTimeout(500); | ||
socket.connect(new InetSocketAddress( | ||
waitStrategyTarget.getHost(), waitStrategyTarget.getFirstMappedPort())); | ||
// Haven't seen any SSH server that sends the version in two or more packets. | ||
//noinspection ResultOfMethodCallIgnored | ||
socket.getInputStream().read(buffer); | ||
if (!Arrays.equals(buffer, "SSH-2.0".getBytes(StandardCharsets.UTF_8))) { | ||
error = new IOException("The version message doesn't look like an SSH server version"); | ||
} | ||
} catch (IOException err) { | ||
error = err; | ||
} | ||
|
||
if (error == null) { | ||
break; | ||
} else if (System.nanoTime() >= expectedEnd) { | ||
throw new RuntimeException(error); | ||
} | ||
|
||
try { | ||
//noinspection BusyWait | ||
Thread.sleep(Math.max(0L, 500L - (System.nanoTime() - attemptStart) / 1_000_000)); | ||
} catch (InterruptedException e) { | ||
Thread.currentThread().interrupt(); | ||
break; | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public WaitStrategy withStartupTimeout(Duration startupTimeout) { | ||
this.startupTimeout = startupTimeout; | ||
return this; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
/* | ||
* Copyright (C)2009 - SSHJ Contributors | ||
* | ||
* 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. | ||
*/ | ||
package com.hierynomus.sshj; | ||
|
||
import net.schmizz.sshj.Config; | ||
import net.schmizz.sshj.DefaultConfig; | ||
import net.schmizz.sshj.SSHClient; | ||
import net.schmizz.sshj.transport.verification.PromiscuousVerifier; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.testcontainers.containers.GenericContainer; | ||
import org.testcontainers.images.builder.ImageFromDockerfile; | ||
import org.testcontainers.images.builder.dockerfile.DockerfileBuilder; | ||
|
||
import java.io.IOException; | ||
import java.nio.file.Paths; | ||
import java.util.concurrent.Future; | ||
|
||
/** | ||
* A JUnit4 rule for launching a generic SSH server container. | ||
*/ | ||
public class SshdContainer extends GenericContainer<SshdContainer> { | ||
@SuppressWarnings("unused") // Used dynamically by Spock | ||
public SshdContainer() { | ||
this(new ImageFromDockerfile() | ||
.withDockerfileFromBuilder(SshdContainer::defaultDockerfileBuilder) | ||
.withFileFromPath(".", Paths.get("src/itest/docker-image"))); | ||
} | ||
|
||
public SshdContainer(@NotNull Future<String> future) { | ||
super(future); | ||
withExposedPorts(22); | ||
setWaitStrategy(new SshServerWaitStrategy()); | ||
} | ||
|
||
public static void defaultDockerfileBuilder(@NotNull DockerfileBuilder builder) { | ||
builder.from("sickp/alpine-sshd:7.5-r2"); | ||
|
||
builder.add("authorized_keys", "/home/sshj/.ssh/authorized_keys"); | ||
|
||
builder.add("test-container/ssh_host_ecdsa_key", "/etc/ssh/ssh_host_ecdsa_key"); | ||
builder.add("test-container/ssh_host_ecdsa_key.pub", "/etc/ssh/ssh_host_ecdsa_key.pub"); | ||
builder.add("test-container/ssh_host_ed25519_key", "/etc/ssh/ssh_host_ed25519_key"); | ||
builder.add("test-container/ssh_host_ed25519_key.pub", "/etc/ssh/ssh_host_ed25519_key.pub"); | ||
builder.add("test-container/sshd_config", "/etc/ssh/sshd_config"); | ||
builder.copy("test-container/trusted_ca_keys", "/etc/ssh/trusted_ca_keys"); | ||
builder.copy("test-container/host_keys/*", "/etc/ssh/"); | ||
|
||
builder.run("apk add --no-cache tini" | ||
+ " && echo \"root:smile\" | chpasswd" | ||
+ " && adduser -D -s /bin/ash sshj" | ||
+ " && passwd -u sshj" | ||
+ " && echo \"sshj:ultrapassword\" | chpasswd" | ||
+ " && chmod 600 /home/sshj/.ssh/authorized_keys" | ||
+ " && chmod 600 /etc/ssh/ssh_host_*_key" | ||
+ " && chmod 644 /etc/ssh/*.pub" | ||
+ " && chown -R sshj:sshj /home/sshj"); | ||
builder.entryPoint("/sbin/tini", "/entrypoint.sh", "-o", "LogLevel=DEBUG2"); | ||
} | ||
|
||
public SSHClient getConnectedClient(Config config) throws IOException { | ||
SSHClient sshClient = new SSHClient(config); | ||
sshClient.addHostKeyVerifier(new PromiscuousVerifier()); | ||
sshClient.connect("127.0.0.1", getFirstMappedPort()); | ||
|
||
return sshClient; | ||
} | ||
|
||
public SSHClient getConnectedClient() throws IOException { | ||
return getConnectedClient(new DefaultConfig()); | ||
} | ||
} |
Oops, something went wrong.