├── .noninteractive
├── .gitattributes
├── test
├── TModule
│ ├── _Data
│ │ ├── file_write.txt
│ │ └── file_read.txt
│ └── SSH2Test.php
├── Callback.php
├── _Simulation
│ ├── EventCollection.php
│ ├── Event.php
│ ├── SimulationInterface.php
│ └── Simulation.php
├── bootstrap.php
├── TUnit
│ ├── Auth
│ │ ├── SSH2AgentTest.php
│ │ ├── SSH2NoneTest.php
│ │ ├── SSH2PasswordTest.php
│ │ ├── SSH2PublicKeyFileTest.php
│ │ └── SSH2HostBasedFileTest.php
│ ├── SSH2ConfigTest.php
│ ├── Driver
│ │ ├── SftpTest.php
│ │ └── ShellTest.php
│ └── SSH2Test.php
├── TModule.php
└── TUnit.php
├── example
├── _file_read.txt
├── _file_write.txt
├── ssh_async_single_command.php
├── sftp_async_read.php
├── sftp_async_write.php
└── ssh_async_multi_command.php
├── .gitignore
├── CHANGELOG.md
├── src
└── SSH
│ ├── SSH2ResourceInterface.php
│ ├── SSH2AuthInterface.php
│ ├── SSH2ConfigInterface.php
│ ├── Auth
│ ├── SSH2None.php
│ ├── SSH2Agent.php
│ ├── SSH2Password.php
│ ├── SSH2PublicKeyFile.php
│ └── SSH2HostBasedFile.php
│ ├── SSH2Config.php
│ ├── SSH2DriverInterface.php
│ ├── SSH2Interface.php
│ ├── SSH2.php
│ └── Driver
│ ├── Sftp.php
│ ├── Sftp
│ └── SftpResource.php
│ ├── Shell
│ └── ShellResource.php
│ └── Shell.php
├── phpunit.xml
├── LICENSE
├── .travis.yml
├── CONTRIBUTING.md
├── composer.json
├── .scrutinizer.yml
└── README.md
/.noninteractive:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
--------------------------------------------------------------------------------
/test/TModule/_Data/file_write.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/example/_file_read.txt:
--------------------------------------------------------------------------------
1 | DAZZLE
2 | IS
3 | AWESOME!
4 |
--------------------------------------------------------------------------------
/example/_file_write.txt:
--------------------------------------------------------------------------------
1 | DAZZLE
2 | IS
3 | AWESOME!
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | vendor
3 | composer.lock
4 | composer.phar
--------------------------------------------------------------------------------
/test/TModule/_Data/file_read.txt:
--------------------------------------------------------------------------------
1 | DAZZLE
2 | IS
3 | AWESOME
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Release Notes
2 |
3 | This changelog references the relevant changes, bug and security fixes done.
4 |
--------------------------------------------------------------------------------
/test/Callback.php:
--------------------------------------------------------------------------------
1 | username = $username;
23 | }
24 |
25 | /**
26 | * @override
27 | * @inheritDoc
28 | */
29 | public function authenticate($conn)
30 | {
31 | return true === @ssh2_auth_none($conn, $this->username);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/test/_Simulation/Event.php:
--------------------------------------------------------------------------------
1 | name = $name;
24 | $this->data = $data;
25 | }
26 |
27 | /**
28 | * @return mixed
29 | */
30 | public function name()
31 | {
32 | return $this->name;
33 | }
34 |
35 | /**
36 | * @return mixed[]
37 | */
38 | public function data()
39 | {
40 | return $this->data;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/SSH/Auth/SSH2Agent.php:
--------------------------------------------------------------------------------
1 | username = $username;
25 | }
26 |
27 | /**
28 | * @override
29 | * @inheritDoc
30 | */
31 | public function authenticate($conn)
32 | {
33 | return @ssh2_auth_agent(
34 | $conn,
35 | $this->username
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/test/TUnit/Auth/SSH2AgentTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(SSH2AuthInterface::class, $auth);
20 | $this->assertAttributeEquals($user, 'username', $auth);
21 | }
22 |
23 | /**
24 | *
25 | */
26 | public function testDestructor_DoesNotThrowException()
27 | {
28 | $user = 'user';
29 | $auth = new SSH2Agent($user);
30 | unset($auth);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/test/TUnit/Auth/SSH2NoneTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(SSH2AuthInterface::class, $auth);
20 | $this->assertAttributeEquals($user, 'username', $auth);
21 | }
22 |
23 | /**
24 | *
25 | */
26 | public function testDestructor_DoesNotThrowException()
27 | {
28 | $user = 'user';
29 | $auth = new SSH2None($user);
30 | unset($auth);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/SSH/Auth/SSH2Password.php:
--------------------------------------------------------------------------------
1 | username = $username;
29 | $this->password = $password;
30 | }
31 |
32 | /**
33 | * @override
34 | * @inheritDoc
35 | */
36 | public function authenticate($conn)
37 | {
38 | return @ssh2_auth_password($conn, $this->username, $this->password);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/test/TUnit/Auth/SSH2PasswordTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(SSH2AuthInterface::class, $auth);
21 | $this->assertAttributeEquals($user, 'username', $auth);
22 | $this->assertAttributeEquals($pass, 'password', $auth);
23 | }
24 |
25 | /**
26 | *
27 | */
28 | public function testDestructor_DoesNotThrowException()
29 | {
30 | $user = 'user';
31 | $pass = 'pass';
32 | $auth = new SSH2Password($user, $pass);
33 | unset($auth);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
234 | "Everything is possible. The impossible just takes longer." ― Dan Brown 235 |
236 | -------------------------------------------------------------------------------- /src/SSH/Driver/Shell/ShellResource.php: -------------------------------------------------------------------------------- 1 | driver = $driver; 75 | $this->resource = $resource; 76 | 77 | $this->paused = true; 78 | $this->closing = false; 79 | $this->readable = true; 80 | $this->writable = true; 81 | 82 | $this->bufferSize = 4096; 83 | 84 | $this->prefix = md5(microtime()); 85 | $this->successSuffix = md5(microtime()); 86 | $this->failureSuffix = md5(microtime()); 87 | } 88 | 89 | /** 90 | * @return string 91 | */ 92 | public function getId() 93 | { 94 | return $this->prefix; 95 | } 96 | 97 | /** 98 | * @return string 99 | */ 100 | public function getPrefix() 101 | { 102 | return $this->prefix; 103 | } 104 | 105 | /** 106 | * @return string 107 | */ 108 | public function getSuccessSuffix() 109 | { 110 | return $this->successSuffix; 111 | } 112 | 113 | /** 114 | * @return string 115 | */ 116 | public function getFailureSuffix() 117 | { 118 | return $this->failureSuffix; 119 | } 120 | 121 | /** 122 | * @override 123 | * @inheritDoc 124 | */ 125 | public function setLoop(LoopInterface $loop = null) 126 | { 127 | $this->driver->setLoop($loop); 128 | } 129 | 130 | /** 131 | * @override 132 | * @inheritDoc 133 | */ 134 | public function getLoop() 135 | { 136 | return $this->driver->getLoop(); 137 | } 138 | 139 | /** 140 | * @override 141 | * @inheritDoc 142 | */ 143 | public function pause() 144 | { 145 | if (!$this->paused) 146 | { 147 | $this->paused = true; 148 | } 149 | } 150 | 151 | /** 152 | * @override 153 | * @inheritDoc 154 | */ 155 | public function resume() 156 | { 157 | if ($this->paused) 158 | { 159 | $this->paused = false; 160 | } 161 | } 162 | 163 | /** 164 | * @override 165 | * @inheritDoc 166 | */ 167 | public function isPaused() 168 | { 169 | return $this->paused; 170 | } 171 | 172 | /** 173 | * @override 174 | * @inheritDoc 175 | */ 176 | public function getResource() 177 | { 178 | return $this->resource; 179 | } 180 | 181 | /** 182 | * @override 183 | * @inheritDoc 184 | */ 185 | public function getResourceId() 186 | { 187 | return (int) $this->resource; 188 | } 189 | 190 | /** 191 | * @override 192 | * @inheritDoc 193 | */ 194 | public function getMetadata() 195 | { 196 | return stream_get_meta_data($this->resource); 197 | } 198 | 199 | /** 200 | * @override 201 | * @inheritDoc 202 | */ 203 | public function getStreamType() 204 | { 205 | return $this->getMetadata()['stream_type']; 206 | } 207 | 208 | /** 209 | * @override 210 | * @inheritDoc 211 | */ 212 | public function getWrapperType() 213 | { 214 | return $this->getMetadata()['wrapper_type']; 215 | } 216 | 217 | /** 218 | * @override 219 | * @inheritDoc 220 | */ 221 | public function isOpen() 222 | { 223 | return !$this->closing; 224 | } 225 | 226 | /** 227 | * @override 228 | * @inheritDoc 229 | */ 230 | public function isSeekable() 231 | { 232 | return $this->getMetadata()['seekable']; 233 | } 234 | 235 | /** 236 | * @override 237 | * @inheritDoc 238 | */ 239 | public function tell() 240 | { 241 | throw new ReadException('Cannot tell offset of this kind of stream.'); 242 | } 243 | 244 | /** 245 | * @override 246 | * @inheritDoc 247 | */ 248 | public function seek($offset, $whence = SEEK_SET) 249 | { 250 | throw new WriteException('Cannot seek on this kind of stream.'); 251 | } 252 | 253 | /** 254 | * @override 255 | * @inheritDoc 256 | */ 257 | public function rewind() 258 | { 259 | throw new WriteException('Cannot rewind this kind of stream.'); 260 | } 261 | 262 | /** 263 | * @override 264 | * @inheritDoc 265 | */ 266 | public function close() 267 | { 268 | if ($this->closing) 269 | { 270 | return; 271 | } 272 | 273 | $this->closing = true; 274 | $this->readable = false; 275 | $this->writable = false; 276 | 277 | $this->emit('close', [ $this ]); 278 | $this->pause(); 279 | $this->emit('done', [ $this ]); 280 | } 281 | 282 | /** 283 | * @override 284 | * @inheritDoc 285 | */ 286 | public function isReadable() 287 | { 288 | return $this->readable; 289 | } 290 | 291 | /** 292 | * @override 293 | * @inheritDoc 294 | */ 295 | public function setBufferSize($bufferSize) 296 | { 297 | $this->bufferSize = $bufferSize; 298 | } 299 | 300 | /** 301 | * @override 302 | * @inheritDoc 303 | */ 304 | public function getBufferSize() 305 | { 306 | return $this->bufferSize; 307 | } 308 | 309 | /** 310 | * @override 311 | * @inheritDoc 312 | */ 313 | public function read($length = null) 314 | { 315 | if (!$this->readable) 316 | { 317 | return $this->throwAndEmitException( 318 | new ReadException('Stream is no longer readable.') 319 | ); 320 | } 321 | 322 | if ($length === null) 323 | { 324 | $length = $this->bufferSize; 325 | } 326 | 327 | $ret = fread($this->resource, $length); 328 | 329 | if ($ret === false) 330 | { 331 | return $this->throwAndEmitException( 332 | new ReadException('Cannot read stream.') 333 | ); 334 | } 335 | else if ($ret !== '') 336 | { 337 | $this->emit('data', [ $this, $ret ]); 338 | 339 | if (strlen($ret) < $length) 340 | { 341 | $this->emit('end', [ $this ]); 342 | } 343 | } 344 | 345 | return $ret; 346 | } 347 | 348 | /** 349 | * @override 350 | * @inheritDoc 351 | */ 352 | public function isWritable() 353 | { 354 | return $this->writable; 355 | } 356 | 357 | /** 358 | * @override 359 | * @inheritDoc 360 | */ 361 | public function write($text = '') 362 | { 363 | if (!$this->writable) 364 | { 365 | return $this->throwAndEmitException( 366 | new WriteException('Stream is no longer writable.') 367 | ); 368 | } 369 | 370 | $command = sprintf( 371 | "echo %s && %s && echo %s || echo %s\n", 372 | $this->prefix, 373 | $text, 374 | $this->successSuffix . ':$?', 375 | $this->failureSuffix . ':$?' 376 | ); 377 | 378 | $sent = fwrite($this->resource, $command); 379 | 380 | if ($sent === false) 381 | { 382 | return $this->throwAndEmitException( 383 | new WriteException('Error occurred while writing to the stream resource.') 384 | ); 385 | } 386 | 387 | $this->writable = false; // this is single-use stream only! 388 | $this->emit('drain', [ $this ]); 389 | $this->emit('finish', [ $this ]); 390 | 391 | return true; 392 | } 393 | 394 | /** 395 | * Emit error event and the throws it too. 396 | * 397 | * @param Error|Exception $ex 398 | * @return null 399 | * @throws Error|Exception 400 | */ 401 | protected function throwAndEmitException($ex) 402 | { 403 | $this->emit('error', [ $this, $ex ]); 404 | throw $ex; 405 | } 406 | } 407 | -------------------------------------------------------------------------------- /src/SSH/Driver/Shell.php: -------------------------------------------------------------------------------- 1 | ssh2 = $ssh2; 85 | $this->conn = $conn; 86 | $this->interval = $interval; 87 | 88 | $this->loop = $ssh2->getLoop(); 89 | 90 | $this->resource = null; 91 | $this->resources = []; 92 | $this->paused = true; 93 | 94 | $this->timer = null; 95 | $this->resourcesCounter = 0; 96 | $this->buffer = ''; 97 | $this->prefix = ''; 98 | 99 | $this->resume(); 100 | } 101 | 102 | /** 103 | * 104 | */ 105 | public function __destruct() 106 | { 107 | $this->disconnect(); 108 | } 109 | 110 | /** 111 | * @override 112 | * @inheritDoc 113 | */ 114 | public function getName() 115 | { 116 | return SSH2::DRIVER_SHELL; 117 | } 118 | 119 | /** 120 | * @override 121 | * @inheritDoc 122 | */ 123 | public function connect() 124 | { 125 | if ($this->resource !== null) 126 | { 127 | return; 128 | } 129 | 130 | $shell = $this->createConnection($this->conn); 131 | 132 | if (!$shell || !is_resource($shell)) 133 | { 134 | $this->emit('error', [ $this, new ExecutionException('SSH2:Shell could not be connected.') ]); 135 | return; 136 | } 137 | 138 | $this->resource = $shell; 139 | 140 | $this->emit('connect', [ $this ]); 141 | } 142 | 143 | /** 144 | * @override 145 | * @inheritDoc 146 | */ 147 | public function disconnect() 148 | { 149 | if ($this->resource === null || !is_resource($this->resource)) 150 | { 151 | return; 152 | } 153 | 154 | $this->pause(); 155 | 156 | foreach ($this->resources as $resource) 157 | { 158 | $resource->close(); 159 | } 160 | 161 | $this->handleDisconnect(); 162 | $this->emit('disconnect', [ $this ]); 163 | } 164 | 165 | /** 166 | * @override 167 | * @inheritDoc 168 | */ 169 | public function isConnected() 170 | { 171 | return $this->resource !== null && is_resource($this->resource); 172 | } 173 | 174 | /** 175 | * @override 176 | * @inheritDoc 177 | */ 178 | public function pause() 179 | { 180 | if (!$this->paused) 181 | { 182 | $this->paused = true; 183 | 184 | if ($this->timer !== null) 185 | { 186 | $this->timer->cancel(); 187 | $this->timer = null; 188 | } 189 | } 190 | } 191 | 192 | /** 193 | * @override 194 | * @inheritDoc 195 | */ 196 | public function resume() 197 | { 198 | if ($this->paused) 199 | { 200 | $this->paused = false; 201 | 202 | if ($this->timer === null) 203 | { 204 | $this->timer = $this->loop->addPeriodicTimer($this->interval, [ $this, 'handleHeartbeat' ]); 205 | } 206 | } 207 | } 208 | 209 | /** 210 | * @override 211 | * @inheritDoc 212 | */ 213 | public function isPaused() 214 | { 215 | return $this->paused; 216 | } 217 | 218 | /** 219 | * @override 220 | * @inheritDoc 221 | */ 222 | public function open($resource = null, $flags = 'r') 223 | { 224 | if (!$this->isConnected()) 225 | { 226 | throw new ResourceUndefinedException('Tried to open resource before establishing SSH2 connection!'); 227 | } 228 | 229 | $resource = new ShellResource($this, $this->resource); 230 | $resource->on('open', function(SSH2ResourceInterface $resource) { 231 | $this->emit('resource:open', [ $this, $resource ]); 232 | }); 233 | $resource->on('close', function(SSH2ResourceInterface $resource) { 234 | $this->removeResource($resource->getId()); 235 | $this->emit('resource:close', [ $this, $resource ]); 236 | }); 237 | 238 | $this->resources[$resource->getId()] = $resource; 239 | $this->resourcesCounter++; 240 | $this->resume(); 241 | 242 | return $resource; 243 | } 244 | 245 | /** 246 | * Handle data. 247 | * 248 | * @internal 249 | */ 250 | public function handleHeartbeat() 251 | { 252 | if (fwrite($this->resource, "\n") === 0) 253 | { 254 | return $this->ssh2->disconnect(); 255 | } 256 | 257 | $this->handleRead(); 258 | } 259 | 260 | /** 261 | * Handle incoming data. 262 | * 263 | * @internal 264 | */ 265 | protected function handleRead() 266 | { 267 | if ($this->paused) 268 | { 269 | return; 270 | } 271 | 272 | $data = @fread($this->resource, static::BUFFER_SIZE); 273 | 274 | if ($data === false || $data === '') 275 | { 276 | return; 277 | } 278 | 279 | $this->buffer .= $data; 280 | 281 | while ($this->buffer !== '') 282 | { 283 | if ($this->prefix !== '' && !isset($this->resources[$this->prefix])) 284 | { 285 | $this->prefix = ''; 286 | } 287 | 288 | if ($this->prefix === '') 289 | { 290 | if (!preg_match('/([a-zA-Z0-9]{32})\r?\n(.*)/s', $this->buffer, $matches)) 291 | { 292 | return; 293 | } 294 | 295 | $this->prefix = $matches[1]; 296 | $this->buffer = $matches[2]; 297 | } 298 | 299 | $resource = $this->resources[$this->prefix]; 300 | $data = ''; 301 | $status = -1; 302 | $successSuffix = $resource->getSuccessSuffix(); 303 | $failureSuffix = $resource->getFailureSuffix(); 304 | 305 | $this->buffer = preg_replace_callback( 306 | sprintf('/(.*)(%s|%s):(\d*)\r?\n/s', $successSuffix, $failureSuffix), 307 | function($matches) use($resource, &$data, &$status) { 308 | $data = $matches[1]; 309 | $status = (int) $matches[3]; 310 | return ''; 311 | }, 312 | $this->buffer 313 | ); 314 | 315 | if ($status === -1) 316 | { 317 | $data = $this->buffer; 318 | $this->buffer = ''; 319 | } 320 | else 321 | { 322 | $this->removeResource($this->prefix); 323 | $this->prefix = ''; 324 | } 325 | 326 | $parts = str_split($data, $resource->getBufferSize()); 327 | unset($data); 328 | 329 | foreach ($parts as &$part) 330 | { 331 | $resource->emit('data', [ $resource, $part ]); 332 | } 333 | unset($parts); 334 | unset($part); 335 | 336 | if ($status === 0) 337 | { 338 | $resource->emit('end', [ $resource ]); 339 | $resource->close(); 340 | } 341 | else if ($status > 0) 342 | { 343 | $resource->emit('error', [ $resource, new ReadException($status) ]); 344 | $resource->close(); 345 | } 346 | } 347 | 348 | $this->handleRead(); 349 | } 350 | 351 | /** 352 | * 353 | */ 354 | protected function handleDisconnect() 355 | { 356 | @fclose($this->resource); 357 | $this->resource = null; 358 | } 359 | 360 | /** 361 | * @param resource $conn 362 | * @return resource 363 | */ 364 | protected function createConnection($conn) 365 | { 366 | return @ssh2_shell($conn); 367 | } 368 | 369 | /** 370 | * Remove resource from known collection. 371 | * 372 | * @param string $prefix 373 | */ 374 | private function removeResource($prefix) 375 | { 376 | if (!isset($this->resources[$prefix])) 377 | { 378 | return; 379 | } 380 | 381 | unset($this->resources[$prefix]); 382 | $this->resourcesCounter--; 383 | 384 | if ($this->resourcesCounter === 0) 385 | { 386 | $this->pause(); 387 | } 388 | } 389 | } 390 | -------------------------------------------------------------------------------- /test/TUnit/Driver/SftpTest.php: -------------------------------------------------------------------------------- 1 | createDriver(); 24 | $this->assertInstanceOf(SSH2DriverInterface::class, $driver); 25 | } 26 | 27 | /** 28 | * 29 | */ 30 | public function testDestructor_DoesNotThrowThrowable() 31 | { 32 | $driver = $this->createDriver(); 33 | unset($driver); 34 | } 35 | 36 | /** 37 | * 38 | */ 39 | public function testApiGetName_ReturnsSftpName() 40 | { 41 | $driver = $this->createDriver(); 42 | 43 | $this->assertSame(SSH2::DRIVER_SFTP, $driver->getName()); 44 | } 45 | 46 | /** 47 | * 48 | */ 49 | public function testApiConnect_DoesNothing_WhenConnectionIsEstablished() 50 | { 51 | $driver = $this->createDriver(); 52 | $this->setProtectedProperty($driver, 'resource', $stream = fopen('php://memory', 'r')); 53 | 54 | $driver->on('error', $this->expectCallableNever()); 55 | $driver->on('connect', $this->expectCallableNever()); 56 | 57 | $driver->connect(); 58 | } 59 | 60 | /** 61 | * 62 | */ 63 | public function testApiConnect_EmitsErrorEvent_WhenConnectionCouldNotBeEstablished_AndReturnedFalse() 64 | { 65 | $driver = $this->createDriver([ 'createConnection' ]); 66 | $driver 67 | ->expects($this->once()) 68 | ->method('createConnection') 69 | ->will($this->returnValue(false)); 70 | 71 | $callback = $this->createCallableMock(); 72 | $callback 73 | ->expects($this->once()) 74 | ->method('__invoke') 75 | ->with($driver, $this->isInstanceOf(ExecutionException::class)); 76 | 77 | $driver->on('error', $callback); 78 | $driver->on('connect', $this->expectCallableNever()); 79 | 80 | $driver->connect(); 81 | } 82 | 83 | /** 84 | * 85 | */ 86 | public function testApiConnect_EmitsErrorEvent_WhenConnectionCouldNotBeEstablished_AndReturnedNotResource() 87 | { 88 | $driver = $this->createDriver([ 'createConnection' ]); 89 | $driver 90 | ->expects($this->once()) 91 | ->method('createConnection') 92 | ->will($this->returnValue(true)); 93 | 94 | $callback = $this->createCallableMock(); 95 | $callback 96 | ->expects($this->once()) 97 | ->method('__invoke') 98 | ->with($driver, $this->isInstanceOf(ExecutionException::class)); 99 | 100 | $driver->on('error', $callback); 101 | $driver->on('connect', $this->expectCallableNever()); 102 | 103 | $driver->connect(); 104 | } 105 | 106 | /** 107 | * 108 | */ 109 | public function testApiConnect_EmitsConnectEvent() 110 | { 111 | $stream = fopen('php://memory', 'r'); 112 | 113 | $driver = $this->createDriver([ 'createConnection' ]); 114 | $driver 115 | ->expects($this->once()) 116 | ->method('createConnection') 117 | ->will($this->returnValue($stream)); 118 | 119 | $callback = $this->createCallableMock(); 120 | $callback 121 | ->expects($this->once()) 122 | ->method('__invoke') 123 | ->with($driver); 124 | 125 | $driver->on('error', $this->expectCallableNever()); 126 | $driver->on('connect', $callback); 127 | 128 | $driver->connect(); 129 | } 130 | 131 | /** 132 | * 133 | */ 134 | public function testApiDisconnect_DoesNothing_WhenConnectionIsNull() 135 | { 136 | $driver = $this->createDriver(); 137 | 138 | $driver->on('disconnect', $this->expectCallableNever()); 139 | 140 | $driver->disconnect(); 141 | } 142 | 143 | /** 144 | * 145 | */ 146 | public function testApiDisconnect_DoesNothing_WhenConnectionIsNotResource() 147 | { 148 | $driver = $this->createDriver(); 149 | $this->setProtectedProperty($driver, 'resource', true); 150 | 151 | $driver->on('disconnect', $this->expectCallableNever()); 152 | 153 | $driver->disconnect(); 154 | } 155 | 156 | /** 157 | * 158 | */ 159 | public function testApiDisconnect_EmitsDisconnectEvent() 160 | { 161 | $driver = $this->createDriver([ 'pause', 'handleDisconnect' ]); 162 | $this->setProtectedProperty($driver, 'resource', $stream = fopen('php://memory', 'r+')); 163 | 164 | $driver 165 | ->expects($this->once()) 166 | ->method('pause'); 167 | $driver 168 | ->expects($this->once()) 169 | ->method('handleDisconnect'); 170 | 171 | $callback = $this->createCallableMock(); 172 | $callback 173 | ->expects($this->once()) 174 | ->method('__invoke') 175 | ->with($driver); 176 | 177 | $driver->on('disconnect', $callback); 178 | 179 | $driver->disconnect(); 180 | } 181 | 182 | /** 183 | * 184 | */ 185 | public function testApiDisconnect_ClosesEachResource() 186 | { 187 | $driver = $this->createDriver([ 'pause', 'handleDisconnect' ]); 188 | 189 | $resource1 = $this->getMock(SftpResource::class, [], [], '', false); 190 | $resource1 191 | ->expects($this->once()) 192 | ->method('close'); 193 | 194 | $resource2 = $this->getMock(SftpResource::class, [], [], '', false); 195 | $resource1 196 | ->expects($this->once()) 197 | ->method('close'); 198 | 199 | $resources = [ $resource1, $resource2 ]; 200 | 201 | $this->setProtectedProperty($driver, 'resource', $stream = fopen('php://memory', 'r+')); 202 | $this->setProtectedProperty($driver, 'resources', $resources); 203 | 204 | $driver 205 | ->expects($this->once()) 206 | ->method('pause'); 207 | $driver 208 | ->expects($this->once()) 209 | ->method('handleDisconnect'); 210 | 211 | $driver->disconnect(); 212 | } 213 | 214 | /** 215 | * 216 | */ 217 | public function testApiIsConnected_ReturnsFalse_WhenConnectionIsNull() 218 | { 219 | $driver = $this->createDriver(); 220 | 221 | $this->assertFalse($driver->isConnected()); 222 | } 223 | 224 | /** 225 | * 226 | */ 227 | public function testApiIsConnected_ReturnsFalse_WhenConnectionIsNotResource() 228 | { 229 | $driver = $this->createDriver(); 230 | $this->setProtectedProperty($driver, 'resource', true); 231 | 232 | $this->assertFalse($driver->isConnected()); 233 | } 234 | 235 | /** 236 | * 237 | */ 238 | public function testApiIsConnected_ReturnsTrue_WhenConnectionIsResource() 239 | { 240 | $driver = $this->createDriver(); 241 | $this->setProtectedProperty($driver, 'resource', $stream = fopen('php://memory', 'r')); 242 | 243 | $this->assertTrue($driver->isConnected()); 244 | } 245 | 246 | /** 247 | * 248 | */ 249 | public function testApiResume_DoesNothing_WhenDriverIsNotPaused() 250 | { 251 | $driver = $this->createDriver(); 252 | $this->setProtectedProperty($driver, 'paused', false); 253 | 254 | $driver->resume(); 255 | 256 | $this->assertFalse($driver->isPaused()); 257 | } 258 | 259 | /** 260 | * 261 | */ 262 | public function testApiResume_ResumesDriver_WhenDriverIsPaused() 263 | { 264 | $driver = $this->createDriver(); 265 | $this->setProtectedProperty($driver, 'paused', true); 266 | 267 | $driver->resume(); 268 | 269 | $this->assertFalse($driver->isPaused()); 270 | } 271 | 272 | /** 273 | * 274 | */ 275 | public function testApiPause_PausesDriver_WhenDriverIsNotPaused() 276 | { 277 | $driver = $this->createDriver(); 278 | $this->setProtectedProperty($driver, 'paused', false); 279 | 280 | $driver->pause(); 281 | 282 | $this->assertTrue($driver->isPaused()); 283 | } 284 | 285 | /** 286 | * 287 | */ 288 | public function testApiPause_DoesNothing_WhenDriverIsPaused() 289 | { 290 | $driver = $this->createDriver(); 291 | $this->setProtectedProperty($driver, 'paused', true); 292 | 293 | $driver->pause(); 294 | 295 | $this->assertTrue($driver->isPaused()); 296 | } 297 | 298 | /** 299 | * 300 | */ 301 | public function testApiIsPaused_ReturnsFalse_WhenDriverIsNotPaused() 302 | { 303 | $driver = $this->createDriver(); 304 | $this->setProtectedProperty($driver, 'paused', false); 305 | 306 | $this->assertFalse($driver->isPaused()); 307 | } 308 | 309 | /** 310 | * 311 | */ 312 | public function testApiIsPaused_ReturnsTrue_WhenDriverIsPaused() 313 | { 314 | $driver = $this->createDriver(); 315 | $this->setProtectedProperty($driver, 'paused', true); 316 | 317 | $this->assertTrue($driver->isPaused()); 318 | } 319 | 320 | /** 321 | * Create Sftp driver. 322 | * 323 | * @param string[] $methods 324 | * @param mixed 325 | * @return Sftp|\PHPUnit_Framework_MockObject_MockObject 326 | */ 327 | public function createDriver($methods = null, $constructorParams = []) 328 | { 329 | if (isset($constructorParams['ssh2'])) 330 | { 331 | $ssh2 = $constructorParams['ssh2']; 332 | } 333 | else 334 | { 335 | $timer = $this->getMock(TimerInterface::class, [], [], '', false); 336 | $loop = $this->getMock(Loop::class, [ 'addPeriodicTimer' ], [], '', false); 337 | $loop 338 | ->expects($this->any()) 339 | ->method('addPeriodicTimer') 340 | ->will($this->returnValue($timer)); 341 | 342 | $ssh2 = $this->getMock(SSH2Interface::class, [], [], '', false); 343 | $ssh2 344 | ->expects($this->any()) 345 | ->method('getLoop') 346 | ->will($this->returnValue($loop)); 347 | } 348 | 349 | $conn = isset($constructorParams['conn']) ? $constructorParams['conn'] : fopen('php://memory', 'r+'); 350 | 351 | $mock = $this->getMock(Sftp::class, $methods, [ $ssh2, $conn ]); 352 | 353 | return $mock; 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /test/TUnit/Driver/ShellTest.php: -------------------------------------------------------------------------------- 1 | createDriver(); 24 | $this->assertInstanceOf(SSH2DriverInterface::class, $driver); 25 | } 26 | 27 | /** 28 | * 29 | */ 30 | public function testDestructor_DoesNotThrowThrowable() 31 | { 32 | $driver = $this->createDriver(); 33 | unset($driver); 34 | } 35 | 36 | /** 37 | * 38 | */ 39 | public function testApiGetName_ReturnsShellName() 40 | { 41 | $driver = $this->createDriver(); 42 | 43 | $this->assertSame(SSH2::DRIVER_SHELL, $driver->getName()); 44 | } 45 | 46 | /** 47 | * 48 | */ 49 | public function testApiConnect_DoesNothing_WhenConnectionIsEstablished() 50 | { 51 | $driver = $this->createDriver(); 52 | $this->setProtectedProperty($driver, 'resource', $stream = fopen('php://memory', 'r')); 53 | 54 | $driver->on('error', $this->expectCallableNever()); 55 | $driver->on('connect', $this->expectCallableNever()); 56 | 57 | $driver->connect(); 58 | } 59 | 60 | /** 61 | * 62 | */ 63 | public function testApiConnect_EmitsErrorEvent_WhenConnectionCouldNotBeEstablished_AndReturnedFalse() 64 | { 65 | $driver = $this->createDriver([ 'createConnection' ]); 66 | $driver 67 | ->expects($this->once()) 68 | ->method('createConnection') 69 | ->will($this->returnValue(false)); 70 | 71 | $callback = $this->createCallableMock(); 72 | $callback 73 | ->expects($this->once()) 74 | ->method('__invoke') 75 | ->with($driver, $this->isInstanceOf(ExecutionException::class)); 76 | 77 | $driver->on('error', $callback); 78 | $driver->on('connect', $this->expectCallableNever()); 79 | 80 | $driver->connect(); 81 | } 82 | 83 | /** 84 | * 85 | */ 86 | public function testApiConnect_EmitsErrorEvent_WhenConnectionCouldNotBeEstablished_AndReturnedNotResource() 87 | { 88 | $driver = $this->createDriver([ 'createConnection' ]); 89 | $driver 90 | ->expects($this->once()) 91 | ->method('createConnection') 92 | ->will($this->returnValue(true)); 93 | 94 | $callback = $this->createCallableMock(); 95 | $callback 96 | ->expects($this->once()) 97 | ->method('__invoke') 98 | ->with($driver, $this->isInstanceOf(ExecutionException::class)); 99 | 100 | $driver->on('error', $callback); 101 | $driver->on('connect', $this->expectCallableNever()); 102 | 103 | $driver->connect(); 104 | } 105 | 106 | /** 107 | * 108 | */ 109 | public function testApiConnect_EmitsConnectEvent() 110 | { 111 | $stream = fopen('php://memory', 'r'); 112 | 113 | $driver = $this->createDriver([ 'createConnection' ]); 114 | $driver 115 | ->expects($this->once()) 116 | ->method('createConnection') 117 | ->will($this->returnValue($stream)); 118 | 119 | $callback = $this->createCallableMock(); 120 | $callback 121 | ->expects($this->once()) 122 | ->method('__invoke') 123 | ->with($driver); 124 | 125 | $driver->on('error', $this->expectCallableNever()); 126 | $driver->on('connect', $callback); 127 | 128 | $driver->connect(); 129 | } 130 | 131 | /** 132 | * 133 | */ 134 | public function testApiDisconnect_DoesNothing_WhenConnectionIsNull() 135 | { 136 | $driver = $this->createDriver(); 137 | 138 | $driver->on('disconnect', $this->expectCallableNever()); 139 | 140 | $driver->disconnect(); 141 | } 142 | 143 | /** 144 | * 145 | */ 146 | public function testApiDisconnect_DoesNothing_WhenConnectionIsNotResource() 147 | { 148 | $driver = $this->createDriver(); 149 | $this->setProtectedProperty($driver, 'resource', true); 150 | 151 | $driver->on('disconnect', $this->expectCallableNever()); 152 | 153 | $driver->disconnect(); 154 | } 155 | 156 | /** 157 | * 158 | */ 159 | public function testApiDisconnect_EmitsDisconnectEvent() 160 | { 161 | $driver = $this->createDriver([ 'pause', 'handleDisconnect' ]); 162 | $this->setProtectedProperty($driver, 'resource', $stream = fopen('php://memory', 'r+')); 163 | 164 | $driver 165 | ->expects($this->once()) 166 | ->method('pause'); 167 | $driver 168 | ->expects($this->once()) 169 | ->method('handleDisconnect'); 170 | 171 | $callback = $this->createCallableMock(); 172 | $callback 173 | ->expects($this->once()) 174 | ->method('__invoke') 175 | ->with($driver); 176 | 177 | $driver->on('disconnect', $callback); 178 | 179 | $driver->disconnect(); 180 | } 181 | 182 | /** 183 | * 184 | */ 185 | public function testApiDisconnect_ClosesEachResource() 186 | { 187 | $driver = $this->createDriver([ 'pause', 'handleDisconnect' ]); 188 | 189 | $resource1 = $this->getMock(ShellResource::class, [], [], '', false); 190 | $resource1 191 | ->expects($this->once()) 192 | ->method('close'); 193 | 194 | $resource2 = $this->getMock(ShellResource::class, [], [], '', false); 195 | $resource1 196 | ->expects($this->once()) 197 | ->method('close'); 198 | 199 | $resources = [ $resource1, $resource2 ]; 200 | 201 | $this->setProtectedProperty($driver, 'resource', $stream = fopen('php://memory', 'r+')); 202 | $this->setProtectedProperty($driver, 'resources', $resources); 203 | 204 | $driver 205 | ->expects($this->once()) 206 | ->method('pause'); 207 | $driver 208 | ->expects($this->once()) 209 | ->method('handleDisconnect'); 210 | 211 | $driver->disconnect(); 212 | } 213 | 214 | /** 215 | * 216 | */ 217 | public function testApiIsConnected_ReturnsFalse_WhenConnectionIsNull() 218 | { 219 | $driver = $this->createDriver(); 220 | 221 | $this->assertFalse($driver->isConnected()); 222 | } 223 | 224 | /** 225 | * 226 | */ 227 | public function testApiIsConnected_ReturnsFalse_WhenConnectionIsNotResource() 228 | { 229 | $driver = $this->createDriver(); 230 | $this->setProtectedProperty($driver, 'resource', true); 231 | 232 | $this->assertFalse($driver->isConnected()); 233 | } 234 | 235 | /** 236 | * 237 | */ 238 | public function testApiIsConnected_ReturnsTrue_WhenConnectionIsResource() 239 | { 240 | $driver = $this->createDriver(); 241 | $this->setProtectedProperty($driver, 'resource', $stream = fopen('php://memory', 'r')); 242 | 243 | $this->assertTrue($driver->isConnected()); 244 | } 245 | 246 | /** 247 | * 248 | */ 249 | public function testApiResume_DoesNothing_WhenDriverIsNotPaused() 250 | { 251 | $driver = $this->createDriver(); 252 | $this->setProtectedProperty($driver, 'paused', false); 253 | 254 | $driver->resume(); 255 | 256 | $this->assertFalse($driver->isPaused()); 257 | } 258 | 259 | /** 260 | * 261 | */ 262 | public function testApiResume_ResumesDriver_WhenDriverIsPaused() 263 | { 264 | $driver = $this->createDriver(); 265 | $this->setProtectedProperty($driver, 'paused', true); 266 | 267 | $driver->resume(); 268 | 269 | $this->assertFalse($driver->isPaused()); 270 | } 271 | 272 | /** 273 | * 274 | */ 275 | public function testApiPause_PausesDriver_WhenDriverIsNotPaused() 276 | { 277 | $driver = $this->createDriver(); 278 | $this->setProtectedProperty($driver, 'paused', false); 279 | 280 | $driver->pause(); 281 | 282 | $this->assertTrue($driver->isPaused()); 283 | } 284 | 285 | /** 286 | * 287 | */ 288 | public function testApiPause_DoesNothing_WhenDriverIsPaused() 289 | { 290 | $driver = $this->createDriver(); 291 | $this->setProtectedProperty($driver, 'paused', true); 292 | 293 | $driver->pause(); 294 | 295 | $this->assertTrue($driver->isPaused()); 296 | } 297 | 298 | /** 299 | * 300 | */ 301 | public function testApiIsPaused_ReturnsFalse_WhenDriverIsNotPaused() 302 | { 303 | $driver = $this->createDriver(); 304 | $this->setProtectedProperty($driver, 'paused', false); 305 | 306 | $this->assertFalse($driver->isPaused()); 307 | } 308 | 309 | /** 310 | * 311 | */ 312 | public function testApiIsPaused_ReturnsTrue_WhenDriverIsPaused() 313 | { 314 | $driver = $this->createDriver(); 315 | $this->setProtectedProperty($driver, 'paused', true); 316 | 317 | $this->assertTrue($driver->isPaused()); 318 | } 319 | 320 | /** 321 | * Create Shell driver. 322 | * 323 | * @param string[] $methods 324 | * @param mixed 325 | * @return Shell|\PHPUnit_Framework_MockObject_MockObject 326 | */ 327 | public function createDriver($methods = null, $constructorParams = []) 328 | { 329 | if (isset($constructorParams['ssh2'])) 330 | { 331 | $ssh2 = $constructorParams['ssh2']; 332 | } 333 | else 334 | { 335 | $timer = $this->getMock(TimerInterface::class, [], [], '', false); 336 | $loop = $this->getMock(Loop::class, [ 'addPeriodicTimer' ], [], '', false); 337 | $loop 338 | ->expects($this->any()) 339 | ->method('addPeriodicTimer') 340 | ->will($this->returnValue($timer)); 341 | 342 | $ssh2 = $this->getMock(SSH2Interface::class, [], [], '', false); 343 | $ssh2 344 | ->expects($this->any()) 345 | ->method('getLoop') 346 | ->will($this->returnValue($loop)); 347 | } 348 | 349 | $conn = isset($constructorParams['conn']) ? $constructorParams['conn'] : fopen('php://memory', 'r+'); 350 | 351 | $mock = $this->getMock(Shell::class, $methods, [ $ssh2, $conn ]); 352 | 353 | return $mock; 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /test/TUnit/SSH2Test.php: -------------------------------------------------------------------------------- 1 | createSSH2(); 25 | $this->assertInstanceOf(SSH2Interface::class, $ssh2); 26 | } 27 | 28 | /** 29 | * 30 | */ 31 | public function testDestructor_DoesNotThrowThrowable() 32 | { 33 | $ssh2 = $this->createSSH2(); 34 | unset($ssh2); 35 | } 36 | 37 | /** 38 | * 39 | */ 40 | public function testApiConnect_DoesNothing_WhenConnectionIsNotNull() 41 | { 42 | $ssh2 = $this->createSSH2(); 43 | $this->setProtectedProperty($ssh2, 'conn', $stream = fopen('php://memory', 'r')); 44 | 45 | $ssh2->on('error', $this->expectCallableNever()); 46 | $ssh2->on('connect', $this->expectCallableNever()); 47 | 48 | $ssh2->connect(); 49 | } 50 | 51 | /** 52 | * 53 | */ 54 | public function testApiConnect_EmitsErrorEvent_WhenConnectionCouldNotBeEstablished_AndReturnedFalse() 55 | { 56 | $ssh2 = $this->createSSH2([ 'createConnection' ]); 57 | $ssh2 58 | ->expects($this->once()) 59 | ->method('createConnection') 60 | ->will($this->returnValue(false)); 61 | 62 | $callback = $this->createCallableMock(); 63 | $callback 64 | ->expects($this->once()) 65 | ->method('__invoke') 66 | ->with($ssh2, $this->isInstanceOf(ExecutionException::class)); 67 | 68 | $ssh2->on('error', $callback); 69 | $ssh2->on('connect', $this->expectCallableNever()); 70 | 71 | $ssh2->connect(); 72 | } 73 | 74 | /** 75 | * 76 | */ 77 | public function testApiConnect_EmitsErrorEvent_WhenConnectionCouldNotBeEstablished_AndReturnedNonResource() 78 | { 79 | $ssh2 = $this->createSSH2([ 'createConnection' ]); 80 | $ssh2 81 | ->expects($this->once()) 82 | ->method('createConnection') 83 | ->will($this->returnValue(true)); 84 | 85 | $callback = $this->createCallableMock(); 86 | $callback 87 | ->expects($this->once()) 88 | ->method('__invoke') 89 | ->with($ssh2, $this->isInstanceOf(ExecutionException::class)); 90 | 91 | $ssh2->on('error', $callback); 92 | $ssh2->on('connect', $this->expectCallableNever()); 93 | 94 | $ssh2->connect(); 95 | } 96 | 97 | /** 98 | * 99 | */ 100 | public function testApiConnect_EmitsErrorEvent_WhenAuthenticationIsInvalid() 101 | { 102 | $stream = fopen('php://memory', 'r'); 103 | 104 | $auth = $this->getMock(SSH2AuthInterface::class, [ 'authenticate' ], [], '', false); 105 | $auth 106 | ->expects($this->once()) 107 | ->method('authenticate') 108 | ->will($this->returnValue(false)); 109 | 110 | $ssh2 = $this->createSSH2([ 'createConnection' ], [ 'auth' => $auth ]); 111 | $ssh2 112 | ->expects($this->once()) 113 | ->method('createConnection') 114 | ->will($this->returnValue($stream)); 115 | 116 | $callback = $this->createCallableMock(); 117 | $callback 118 | ->expects($this->once()) 119 | ->method('__invoke') 120 | ->with($ssh2, $this->isInstanceOf(ExecutionException::class)); 121 | 122 | $ssh2->on('error', $callback); 123 | $ssh2->on('connect', $this->expectCallableNever()); 124 | 125 | $ssh2->connect(); 126 | } 127 | 128 | /** 129 | * 130 | */ 131 | public function testApiConnect_EmitsConnectEvent() 132 | { 133 | $stream = fopen('php://memory', 'r'); 134 | 135 | $auth = $this->getMock(SSH2AuthInterface::class, [ 'authenticate' ], [], '', false); 136 | $auth 137 | ->expects($this->once()) 138 | ->method('authenticate') 139 | ->will($this->returnValue(true)); 140 | 141 | $ssh2 = $this->createSSH2([ 'createConnection' ], [ 'auth' => $auth ]); 142 | $ssh2 143 | ->expects($this->once()) 144 | ->method('createConnection') 145 | ->will($this->returnValue($stream)); 146 | 147 | $callback = $this->createCallableMock(); 148 | $callback 149 | ->expects($this->once()) 150 | ->method('__invoke') 151 | ->with($ssh2); 152 | 153 | $ssh2->on('error', $this->expectCallableNever()); 154 | $ssh2->on('connect', $callback); 155 | 156 | $ssh2->connect(); 157 | } 158 | 159 | /** 160 | * 161 | */ 162 | public function testApiDisconnect_DoesNothing_WhenConnectionIsNull() 163 | { 164 | $ssh2 = $this->createSSH2(); 165 | 166 | $ssh2->on('disconnect', $this->expectCallableNever()); 167 | 168 | $ssh2->disconnect(); 169 | } 170 | 171 | /** 172 | * 173 | */ 174 | public function testApiDisconnect_DoesNothing_WhenConnectionIsNotResource() 175 | { 176 | $ssh2 = $this->createSSH2(); 177 | $this->setProtectedProperty($ssh2, 'conn', true); 178 | 179 | $ssh2->on('disconnect', $this->expectCallableNever()); 180 | 181 | $ssh2->disconnect(); 182 | } 183 | 184 | /** 185 | * 186 | */ 187 | public function testApiDisconnect_EmitsDisconnectEvent() 188 | { 189 | $ssh2 = $this->createSSH2(); 190 | $this->setProtectedProperty($ssh2, 'conn', $stream = fopen('php://memory', 'r')); 191 | 192 | $callback = $this->createCallableMock(); 193 | $callback 194 | ->expects($this->once()) 195 | ->method('__invoke') 196 | ->with($ssh2); 197 | 198 | $ssh2->on('disconnect', $callback); 199 | 200 | $ssh2->disconnect(); 201 | } 202 | 203 | /** 204 | * 205 | */ 206 | public function testApiDisconnect_CallsDisconnectMethodAndRemovesListeners_OnEachDriver() 207 | { 208 | $ssh2 = $this->createSSH2(); 209 | 210 | $stream = fopen('php://memory', 'r'); 211 | 212 | $driver1 = $this->getMock(SSH2DriverInterface::class, [], [], '', false); 213 | $driver1 214 | ->expects($this->once()) 215 | ->method('disconnect'); 216 | $driver1 217 | ->expects($this->exactly(3)) 218 | ->method('removeListener') 219 | ->with($this->isType('string'), $this->isType('callable')); 220 | 221 | $driver2 = $this->getMock(SSH2DriverInterface::class, [], [], '', false); 222 | $driver2 223 | ->expects($this->once()) 224 | ->method('disconnect'); 225 | $driver2 226 | ->expects($this->exactly(3)) 227 | ->method('removeListener') 228 | ->with($this->isType('string'), $this->isType('callable')); 229 | 230 | $drivers = [ 'd1' => $driver1, 'd2' => $driver2 ]; 231 | 232 | $this->setProtectedProperty($ssh2, 'conn', $stream); 233 | $this->setProtectedProperty($ssh2, 'drivers', $drivers); 234 | 235 | $callback = $this->createCallableMock(); 236 | $callback 237 | ->expects($this->once()) 238 | ->method('__invoke') 239 | ->with($ssh2); 240 | 241 | $ssh2->on('disconnect', $callback); 242 | 243 | $ssh2->disconnect(); 244 | } 245 | 246 | /** 247 | * 248 | */ 249 | public function testApiIsConnected_ReturnsFalse_WhenConnectionIsNull() 250 | { 251 | $ssh2 = $this->createSSH2(); 252 | 253 | $this->assertFalse($ssh2->isConnected()); 254 | } 255 | 256 | /** 257 | * 258 | */ 259 | public function testApiIsConnected_ReturnsFalse_WhenConnectionIsNotResource() 260 | { 261 | $ssh2 = $this->createSSH2(); 262 | $this->setProtectedProperty($ssh2, 'conn', true); 263 | 264 | $this->assertFalse($ssh2->isConnected()); 265 | } 266 | 267 | /** 268 | * 269 | */ 270 | public function testApiIsConnected_ReturnsTrue_WhenConnectionIsResource() 271 | { 272 | $ssh2 = $this->createSSH2(); 273 | $this->setProtectedProperty($ssh2, 'conn', $stream = fopen('php://memory', 'r')); 274 | 275 | $this->assertTrue($ssh2->isConnected()); 276 | } 277 | 278 | /** 279 | * 280 | */ 281 | public function testApiCreateDriver_ReturnsDriver_WhenDriverDoesExist() 282 | { 283 | $ssh2 = $this->createSSH2(); 284 | 285 | $driver = $this->getMock(SSH2DriverInterface::class, [], [], '', false); 286 | $drivers = [ 'name' => $driver ]; 287 | 288 | $this->setProtectedProperty($ssh2, 'drivers', $drivers); 289 | 290 | $this->assertSame($driver, $ssh2->createDriver('name')); 291 | } 292 | 293 | /** 294 | * 295 | */ 296 | public function testApiCreateDriver_ThrowsException_WhenConnectionIsNotEstablished() 297 | { 298 | $ssh2 = $this->createSSH2(); 299 | 300 | $this->setExpectedException(ExecutionException::class); 301 | $ssh2->createDriver('name'); 302 | } 303 | 304 | /** 305 | * 306 | */ 307 | public function testApiCreateDriver_ThrowsException_WhenInvalidDriverIsRequested() 308 | { 309 | $ssh2 = $this->createSSH2([ 'isConnected' ]); 310 | $ssh2 311 | ->expects($this->once()) 312 | ->method('isConnected') 313 | ->will($this->returnValue(true)); 314 | 315 | $this->setExpectedException(InvalidArgumentException::class); 316 | $ssh2->createDriver('invalidDriver'); 317 | } 318 | 319 | /** 320 | * 321 | */ 322 | public function testApiCreateDriver_CreatesDriver_WhenShellIsRequested() 323 | { 324 | $ssh2 = $this->createSSH2([ 'isConnected' ]); 325 | $ssh2 326 | ->expects($this->once()) 327 | ->method('isConnected') 328 | ->will($this->returnValue(true)); 329 | 330 | $driver = $ssh2->createDriver(SSH2::DRIVER_SHELL); 331 | 332 | $this->assertInstanceOf(SSH2DriverInterface::class, $driver); 333 | $this->assertInstanceOf(Shell::class, $driver); 334 | } 335 | 336 | /** 337 | * 338 | */ 339 | public function testApiCreateDriver_CreatesDriver_WhenSftpIsRequested() 340 | { 341 | $ssh2 = $this->createSSH2([ 'isConnected' ]); 342 | $ssh2 343 | ->expects($this->once()) 344 | ->method('isConnected') 345 | ->will($this->returnValue(true)); 346 | 347 | $driver = $ssh2->createDriver(SSH2::DRIVER_SFTP); 348 | 349 | $this->assertInstanceOf(SSH2DriverInterface::class, $driver); 350 | $this->assertInstanceOf(Sftp::class, $driver); 351 | } 352 | 353 | /** 354 | * Create SSH2 driver. 355 | * 356 | * @param string[]|null $methods 357 | * @param mixed 358 | * @return SSH2|\PHPUnit_Framework_MockObject_MockObject 359 | */ 360 | public function createSSH2($methods = null, $constructorParams = []) 361 | { 362 | $loop = isset($constructorParams['loop']) 363 | ? $constructorParams['loop'] 364 | : $this->getMock(LoopInterface::class, [], [], '', false); 365 | 366 | $auth = isset($constructorParams['auth']) 367 | ? $constructorParams['auth'] 368 | : $this->getMock(SSH2AuthInterface::class, [], [], '', false); 369 | 370 | $config = new SSH2Config(); 371 | 372 | return $this->getMock(SSH2::class, $methods, [ $auth, $config, $loop ]); 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /test/TModule/SSH2Test.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('Test has been skipped because of lacking SSH2 extension!'); 25 | } 26 | 27 | $sim = $this; 28 | $sim = $sim->simulate(function(SimulationInterface $sim) { 29 | $loop = $sim->getLoop(); 30 | $auth = new SSH2Password(TEST_USER, TEST_PASSWORD); 31 | $config = new SSH2Config(); 32 | $ssh2 = new SSH2($auth, $config, $loop); 33 | 34 | $ssh2->on('connect', function(SSH2Interface $ssh) use($sim) { 35 | $sim->expect('connect'); 36 | $ssh->disconnect(); 37 | }); 38 | 39 | $ssh2->on('disconnect', function(SSH2Interface $ssh) use($sim) { 40 | $sim->expect('disconnect'); 41 | $sim->done(); 42 | }); 43 | 44 | $ssh2->on('error', function(SSH2Interface $ssh, $ex) use($sim) { 45 | $sim->fail($ex->getMessage()); 46 | }); 47 | 48 | $sim->onStart(function() use($ssh2) { 49 | $ssh2->connect(); 50 | }); 51 | $sim->onStop(function() use($ssh2) { 52 | $ssh2->disconnect(); 53 | }); 54 | 55 | }); 56 | $sim = $sim->expect([ 57 | [ 'connect', [] ], 58 | [ 'disconnect', [] ] 59 | ]); 60 | } 61 | 62 | /** 63 | * 64 | */ 65 | public function testSSH2_ShellDriver_IsAbleToExecute_SingleLineCommand() 66 | { 67 | if (!extension_loaded('ssh2')) 68 | { 69 | $this->markTestSkipped('Test has been skipped because of lacking SSH2 extension!'); 70 | } 71 | 72 | $sim = $this; 73 | $sim = $sim->simulate(function(SimulationInterface $sim) { 74 | $loop = $sim->getLoop(); 75 | $auth = new SSH2Password(TEST_USER, TEST_PASSWORD); 76 | $config = new SSH2Config(); 77 | $ssh2 = new SSH2($auth, $config, $loop); 78 | 79 | $ssh2->on('connect:shell', function(SSH2DriverInterface $shell) use($sim) { 80 | $sim->expect('connect:shell'); 81 | $buffer = ''; 82 | 83 | $command = $shell->open(); 84 | $command->write('echo "test"'); 85 | $command->on('data', function($command, $data) use(&$buffer) { 86 | $buffer .= $data; 87 | }); 88 | $command->on('end', function() use(&$buffer, $shell, $sim) { 89 | $sim->expect('buffer', [ $buffer ]); 90 | $shell->disconnect(); 91 | }); 92 | }); 93 | 94 | $ssh2->on('disconnect:shell', function(SSH2DriverInterface $shell) use($sim, $ssh2) { 95 | $sim->expect('disconnect:shell'); 96 | $ssh2->disconnect(); 97 | }); 98 | 99 | $ssh2->on('connect', function(SSH2Interface $ssh) use($sim) { 100 | $sim->expect('connect'); 101 | $ssh->createDriver(SSH2::DRIVER_SHELL) 102 | ->connect(); 103 | }); 104 | 105 | $ssh2->on('disconnect', function(SSH2Interface $ssh) use($sim) { 106 | $sim->expect('disconnect'); 107 | $sim->done(); 108 | }); 109 | 110 | $ssh2->on('error', function(SSH2Interface $ssh, $ex) use($sim) { 111 | $sim->fail($ex->getMessage()); 112 | }); 113 | 114 | $sim->onStart(function() use($ssh2) { 115 | $ssh2->connect(); 116 | }); 117 | $sim->onStop(function() use($ssh2) { 118 | $ssh2->disconnect(); 119 | }); 120 | }); 121 | $sim = $sim->expect([ 122 | [ 'connect', [] ], 123 | [ 'connect:shell', [] ], 124 | [ 'buffer', [ "test\r\n" ] ], 125 | [ 'disconnect:shell', [] ], 126 | [ 'disconnect', [] ] 127 | ]); 128 | } 129 | 130 | /** 131 | * 132 | */ 133 | public function testSSH2_ShellDriver_IsAbleToExecute_MultiLineCommand() 134 | { 135 | if (!extension_loaded('ssh2')) 136 | { 137 | $this->markTestSkipped('Test has been skipped because of lacking SSH2 extension!'); 138 | } 139 | 140 | $sim = $this; 141 | $sim = $sim->simulate(function(SimulationInterface $sim) { 142 | $loop = $sim->getLoop(); 143 | $auth = new SSH2Password(TEST_USER, TEST_PASSWORD); 144 | $config = new SSH2Config(); 145 | $ssh2 = new SSH2($auth, $config, $loop); 146 | 147 | $ssh2->on('connect:shell', function(SSH2DriverInterface $shell) use($sim) { 148 | $sim->expect('connect:shell'); 149 | 150 | $buffer = ''; 151 | $command = $shell->open(); 152 | $command->write('printf "A\nB\nC\n"'); 153 | $command->on('data', function($command, $data) use(&$buffer) { 154 | $buffer .= $data; 155 | }); 156 | $command->on('end', function() use(&$buffer, $shell, $sim) { 157 | $sim->expect('buffer', [ $buffer ]); 158 | $shell->disconnect(); 159 | }); 160 | }); 161 | 162 | $ssh2->on('disconnect:shell', function(SSH2DriverInterface $shell) use($sim, $ssh2) { 163 | $sim->expect('disconnect:shell'); 164 | $ssh2->disconnect(); 165 | }); 166 | 167 | $ssh2->on('connect', function(SSH2Interface $ssh) use($sim) { 168 | $sim->expect('connect'); 169 | $ssh->createDriver(SSH2::DRIVER_SHELL) 170 | ->connect(); 171 | }); 172 | 173 | $ssh2->on('disconnect', function(SSH2Interface $ssh) use($sim) { 174 | $sim->expect('disconnect'); 175 | $sim->done(); 176 | }); 177 | 178 | $ssh2->on('error', function(SSH2Interface $ssh, $ex) use($sim) { 179 | $sim->fail($ex->getMessage()); 180 | }); 181 | 182 | $sim->onStart(function() use($ssh2) { 183 | $ssh2->connect(); 184 | }); 185 | $sim->onStop(function() use($ssh2) { 186 | $ssh2->disconnect(); 187 | }); 188 | }); 189 | $sim = $sim->expect([ 190 | [ 'connect', [] ], 191 | [ 'connect:shell', [] ], 192 | [ 'buffer', [ "A\r\nB\r\nC\r\n" ] ], 193 | [ 'disconnect:shell', [] ], 194 | [ 'disconnect', [] ] 195 | ]); 196 | } 197 | 198 | /** 199 | * 200 | */ 201 | public function testSSH2_SftpDriver_IsAbleToWriteFiles() 202 | { 203 | $this->markTestSkipped('It seems there are problems with setting this automation on Travis.'); 204 | 205 | $sim = $this; 206 | $sim = $sim->simulate(function(SimulationInterface $sim) { 207 | $loop = $sim->getLoop(); 208 | $auth = new SSH2Password(TEST_USER, TEST_PASSWORD); 209 | $config = new SSH2Config(); 210 | $ssh2 = new SSH2($auth, $config, $loop); 211 | 212 | $ssh2->on('connect:sftp', function(SSH2DriverInterface $sftp) use($sim) { 213 | $sim->expect('connect:sftp'); 214 | 215 | $lines = [ "DAZZLE\n", "IS\n", "AWESOME!\n" ]; 216 | $linesPointer = 0; 217 | 218 | $file = $sftp->open(__DIR__ . '/_Data/file_write.txt', 'w+'); 219 | $file->write(); 220 | $file->on('drain', function(SSH2ResourceInterface $file) use(&$lines, &$linesPointer) { 221 | if ($linesPointer < count($lines)) { 222 | $file->write($lines[$linesPointer++]); 223 | } 224 | }); 225 | $file->on('finish', function(SSH2ResourceInterface $file) { 226 | $file->close(); 227 | }); 228 | $file->on('close', function(SSH2ResourceInterface $file) use($sftp) { 229 | $sftp->disconnect(); 230 | }); 231 | }); 232 | 233 | $ssh2->on('disconnect:sftp', function(SSH2DriverInterface $sftp) use($sim, $ssh2) { 234 | $sim->expect('disconnect:sftp'); 235 | $ssh2->disconnect(); 236 | }); 237 | 238 | $ssh2->on('connect', function(SSH2Interface $ssh) use($sim) { 239 | $sim->expect('connect'); 240 | $ssh->createDriver(SSH2::DRIVER_SFTP) 241 | ->connect(); 242 | }); 243 | 244 | $ssh2->on('disconnect', function(SSH2Interface $ssh) use($sim) { 245 | $sim->expect('disconnect'); 246 | $sim->done(); 247 | }); 248 | 249 | $ssh2->on('error', function(SSH2Interface $ssh, $ex) use($sim) { 250 | $sim->fail($ex->getMessage()); 251 | }); 252 | 253 | $sim->onStart(function() use($ssh2) { 254 | $ssh2->connect(); 255 | }); 256 | $sim->onStop(function() use($ssh2) { 257 | $ssh2->disconnect(); 258 | }); 259 | }); 260 | $sim = $sim->expect([ 261 | [ 'connect', [] ], 262 | [ 'connect:sftp', [] ], 263 | [ 'disconnect:sftp', [] ], 264 | [ 'disconnect', [] ] 265 | ]); 266 | } 267 | 268 | /** 269 | * 270 | */ 271 | public function testSSH2_SftpDriver_IsAbleToReadFiles() 272 | { 273 | $this->markTestSkipped('It seems there are problems with setting this automation on Travis.'); 274 | 275 | $sim = $this; 276 | $sim = $sim->simulate(function(SimulationInterface $sim) { 277 | $loop = $sim->getLoop(); 278 | $auth = new SSH2Password(TEST_USER, TEST_PASSWORD); 279 | $config = new SSH2Config(); 280 | $ssh2 = new SSH2($auth, $config, $loop); 281 | 282 | $ssh2->on('connect:sftp', function(SSH2DriverInterface $sftp) use($sim) { 283 | $sim->expect('connect:sftp'); 284 | 285 | $buffer = ''; 286 | $file = $sftp->open(__DIR__ . '/_Data/file_read.txt', 'r+'); 287 | $file->read(); 288 | $file->on('data', function(SSH2ResourceInterface $file, $data) use(&$buffer) { 289 | $buffer .= $data; 290 | }); 291 | $file->on('end', function(SSH2ResourceInterface $file) use(&$buffer, $sim) { 292 | $sim->expect('buffer', [ $buffer ]); 293 | $file->close(); 294 | }); 295 | $file->on('close', function(SSH2ResourceInterface $file) use($sftp) { 296 | $sftp->disconnect(); 297 | }); 298 | }); 299 | 300 | $ssh2->on('disconnect:sftp', function(SSH2DriverInterface $sftp) use($sim, $ssh2) { 301 | $sim->expect('disconnect:sftp'); 302 | $ssh2->disconnect(); 303 | }); 304 | 305 | $ssh2->on('connect', function(SSH2Interface $ssh) use($sim) { 306 | $sim->expect('connect'); 307 | $ssh->createDriver(SSH2::DRIVER_SFTP) 308 | ->connect(); 309 | }); 310 | 311 | $ssh2->on('disconnect', function(SSH2Interface $ssh) use($sim) { 312 | $sim->expect('disconnect'); 313 | $sim->done(); 314 | }); 315 | 316 | $ssh2->on('error', function(SSH2Interface $ssh, $ex) use($sim) { 317 | $sim->fail($ex->getMessage()); 318 | }); 319 | 320 | $sim->onStart(function() use($ssh2) { 321 | $ssh2->connect(); 322 | }); 323 | $sim->onStop(function() use($ssh2) { 324 | $ssh2->disconnect(); 325 | }); 326 | }); 327 | $sim = $sim->expect([ 328 | [ 'connect', [] ], 329 | [ 'connect:sftp', [] ], 330 | [ 'buffer', [ "DAZZLE\r\nIS\r\nAWESOME\r\n" ] ], 331 | [ 'disconnect:sftp', [] ], 332 | [ 'disconnect', [] ] 333 | ]); 334 | } 335 | } 336 | --------------------------------------------------------------------------------