diff --git a/src/Dialog/AbstractDialog.php b/src/Dialog/AbstractDialog.php index d3ce60b..c9c882c 100644 --- a/src/Dialog/AbstractDialog.php +++ b/src/Dialog/AbstractDialog.php @@ -2,8 +2,6 @@ namespace Clue\React\Zenity\Dialog; -use React\Promise\Deferred; -use React\ChildProcess\Process; use Clue\React\Zenity\Zen\BaseZen; /** @@ -204,14 +202,12 @@ protected function decamelize($name) /** * Create a dialog handler to process the results for this dialog. * - * @param Deferred $deferred - * @param Process $process * @return BaseZen * @internal */ - public function createZen(Deferred $deferred, Process $process) + public function createZen() { - return new BaseZen($deferred, $process); + return new BaseZen(); } /** diff --git a/src/Dialog/CalendarDialog.php b/src/Dialog/CalendarDialog.php index 497c3d2..f01bce3 100644 --- a/src/Dialog/CalendarDialog.php +++ b/src/Dialog/CalendarDialog.php @@ -4,8 +4,6 @@ use Clue\React\Zenity\Dialog\AbstractTextDialog; use DateTime; -use React\Promise\Deferred; -use React\ChildProcess\Process; use Clue\React\Zenity\Zen\CalendarZen; /** @@ -86,8 +84,8 @@ public function setDateTime(DateTime $date) return $this; } - public function createZen(Deferred $deferred, Process $process) + public function createZen() { - return new CalendarZen($deferred, $process); + return new CalendarZen(); } } diff --git a/src/Dialog/ColorSelectionDialog.php b/src/Dialog/ColorSelectionDialog.php index 72678ae..9590e22 100644 --- a/src/Dialog/ColorSelectionDialog.php +++ b/src/Dialog/ColorSelectionDialog.php @@ -3,8 +3,6 @@ namespace Clue\React\Zenity\Dialog; use Clue\React\Zenity\Dialog\AbstractDialog; -use React\Promise\Deferred; -use React\ChildProcess\Process; use Clue\React\Zenity\Zen\ColorSelectionZen; /** @@ -43,8 +41,8 @@ public function setShowPalette($palette) return $this; } - public function createZen(Deferred $deferred, Process $process) + public function createZen() { - return new ColorSelectionZen($deferred, $process); + return new ColorSelectionZen(); } } diff --git a/src/Dialog/FileSelectionDialog.php b/src/Dialog/FileSelectionDialog.php index 5f669f8..ae8bc5a 100644 --- a/src/Dialog/FileSelectionDialog.php +++ b/src/Dialog/FileSelectionDialog.php @@ -3,8 +3,6 @@ namespace Clue\React\Zenity\Dialog; use Clue\React\Zenity\Dialog\AbstractDialog; -use React\Promise\Deferred; -use React\ChildProcess\Process; use Clue\React\Zenity\Zen\FileSelectionZen; /** @@ -122,8 +120,8 @@ public function setFileFilter($filter) return $this; } - public function createZen(Deferred $deferred, Process $process) + public function createZen() { - return new FileSelectionZen($deferred, $process, $this->multiple, $this->separator); + return new FileSelectionZen($this->multiple, $this->separator); } } diff --git a/src/Dialog/FormsDialog.php b/src/Dialog/FormsDialog.php index dac4470..e680c33 100644 --- a/src/Dialog/FormsDialog.php +++ b/src/Dialog/FormsDialog.php @@ -3,8 +3,6 @@ namespace Clue\React\Zenity\Dialog; use Clue\React\Zenity\Dialog\AbstractTextDialog; -use React\Promise\Deferred; -use React\ChildProcess\Process; use Clue\React\Zenity\Zen\FormsZen; /** @@ -153,8 +151,8 @@ public function getArgs() return array_merge(parent::getArgs(), $this->fields); } - public function createZen(Deferred $deferred, Process $process) + public function createZen() { - return new FormsZen($deferred, $process, $this->separator); + return new FormsZen($this->separator); } } diff --git a/src/Dialog/ListDialog.php b/src/Dialog/ListDialog.php index c5bd25d..4f9e01b 100644 --- a/src/Dialog/ListDialog.php +++ b/src/Dialog/ListDialog.php @@ -3,8 +3,6 @@ namespace Clue\React\Zenity\Dialog; use Clue\React\Zenity\Dialog\AbstractTextDialog; -use React\Promise\Deferred; -use React\ChildProcess\Process; use Clue\React\Zenity\Zen\ListZen; /** @@ -204,10 +202,10 @@ public function getArgs() return array_merge(parent::getArgs(), $this->columns); } - public function createZen(Deferred $deferred, Process $process) + public function createZen() { $single = (!$this->multiple && !$this->checklist); - return new ListZen($deferred, $process, $single, $this->separator); + return new ListZen($single, $this->separator); } } diff --git a/src/Dialog/NotificationDialog.php b/src/Dialog/NotificationDialog.php index 4f5a6af..a5f5454 100644 --- a/src/Dialog/NotificationDialog.php +++ b/src/Dialog/NotificationDialog.php @@ -4,8 +4,6 @@ use Clue\React\Zenity\Dialog\AbstractTextDialog; use Clue\React\Zenity\Zen\NotificationZen; -use React\Promise\Deferred; -use React\ChildProcess\Process; /** * Use the --notification option to create a notification icon. @@ -32,8 +30,8 @@ public function setListen($listen) return $this; } - public function createZen(Deferred $deferred, Process $process) + public function createZen() { - return new NotificationZen($deferred, $process); + return new NotificationZen(); } } diff --git a/src/Dialog/PasswordDialog.php b/src/Dialog/PasswordDialog.php index 727895d..12eca5b 100644 --- a/src/Dialog/PasswordDialog.php +++ b/src/Dialog/PasswordDialog.php @@ -3,8 +3,6 @@ namespace Clue\React\Zenity\Dialog; use Clue\React\Zenity\Dialog\AbstractDialog; -use React\Promise\Deferred; -use React\ChildProcess\Process; use Clue\React\Zenity\Zen\PasswordZen; /** @@ -50,8 +48,8 @@ public function setUsername($username) return $this; } - public function createZen(Deferred $deferred, Process $process) + public function createZen() { - return new PasswordZen($deferred, $process, $this->username); + return new PasswordZen($this->username); } } diff --git a/src/Dialog/ProgressDialog.php b/src/Dialog/ProgressDialog.php index eccee6b..c656b50 100644 --- a/src/Dialog/ProgressDialog.php +++ b/src/Dialog/ProgressDialog.php @@ -4,8 +4,6 @@ use Clue\React\Zenity\Dialog\AbstractTextDialog; use Clue\React\Zenity\Zen\ProgressZen; -use React\Promise\Deferred; -use React\ChildProcess\Process; /** * Use the --progress option to create a progress dialog. @@ -79,8 +77,8 @@ public function setNoCancel($noc) return $this; } - public function createZen(Deferred $deferred, Process $process) + public function createZen() { - return new ProgressZen($deferred, $process, $this->percentage); + return new ProgressZen($this->percentage); } } diff --git a/src/Dialog/ScaleDialog.php b/src/Dialog/ScaleDialog.php index ea59133..5fd9945 100644 --- a/src/Dialog/ScaleDialog.php +++ b/src/Dialog/ScaleDialog.php @@ -3,8 +3,6 @@ namespace Clue\React\Zenity\Dialog; use Clue\React\Zenity\Dialog\AbstractTextDialog; -use React\Promise\Deferred; -use React\ChildProcess\Process; use Clue\React\Zenity\Zen\ScaleZen; /** @@ -90,8 +88,8 @@ public function setHideValue($toggle = true) return $this; } - public function createZen(Deferred $deferred, Process $process) + public function createZen() { - return new ScaleZen($deferred, $process); + return new ScaleZen(); } } diff --git a/src/Dialog/TextInfoDialog.php b/src/Dialog/TextInfoDialog.php index c046da8..46819b1 100644 --- a/src/Dialog/TextInfoDialog.php +++ b/src/Dialog/TextInfoDialog.php @@ -4,8 +4,6 @@ use Clue\React\Zenity\Dialog\AbstractDialog; use Clue\React\Zenity\Zen\TextInfoZen; -use React\Promise\Deferred; -use React\ChildProcess\Process; /** * Use the --text-info option to create a text information dialog. @@ -81,8 +79,8 @@ public function addLine($line) return $this; } - public function createZen(Deferred $deferred, Process $process) + public function createZen() { - return new TextInfoZen($deferred, $process); + return new TextInfoZen(); } } diff --git a/src/Launcher.php b/src/Launcher.php index 9994900..4ceb802 100644 --- a/src/Launcher.php +++ b/src/Launcher.php @@ -46,31 +46,8 @@ public function launch(AbstractDialog $dialog) $process->stdin->write($inbuffer); } - $deferred = new Deferred(); - - $result = null; - $process->stdout->on('data', function ($data) use (&$result) { - if ($data !== '') { - $result .= $data; - } - }); - - $zen = $dialog->createZen($deferred, $process); - - $process->on('exit', function($code) use ($process, $zen, &$result, $deferred) { - if ($code !== 0) { - $deferred->reject($code); - } else { - if ($result === null) { - $result = true; - } else { - $result = $zen->parseValue(trim($result)); - } - $deferred->resolve($result); - } - - $zen->close(); - }); + $zen = $dialog->createZen(); + $zen->go($process); return $zen; } diff --git a/src/Zen/BaseZen.php b/src/Zen/BaseZen.php index 4fd5e71..1d21327 100644 --- a/src/Zen/BaseZen.php +++ b/src/Zen/BaseZen.php @@ -3,29 +3,58 @@ namespace Clue\React\Zenity\Zen; use React\Promise\PromiseInterface; -use React\Promise\Deferred; use React\ChildProcess\Process; +use React\Promise\Deferred; class BaseZen implements PromiseInterface { - private $deferred; - private $process; + protected $promise; + protected $deferred; + protected $process; - public function __construct(Deferred $deferred, Process $process) + /** @internal */ + public function go(Process $process) { - $this->deferred = $deferred; + $this->deferred = $deferred = new Deferred(); $this->process = $process; + + $buffered = null; + $process->stdout->on('data', function ($data) use (&$buffered) { + if ($data !== '') { + $buffered .= $data; + } + }); + + $process->on('exit', function($code) use ($deferred) { + if ($code !== 0) { + $deferred->reject($code); + } else { + $deferred->resolve(); + } + }); + + $that = $this; + $this->promise = $deferred->promise()->then(function () use (&$buffered, $that) { + if ($buffered === null) { + $buffered = true; + } else { + $buffered = $that->parseValue(trim($buffered)); + } + return $buffered; + }); } public function then($fulfilledHandler = null, $errorHandler = null, $progressHandler = null) { - return $this->deferred->then($fulfilledHandler, $errorHandler, $progressHandler); + return $this->promise->then($fulfilledHandler, $errorHandler, $progressHandler); } public function close() { - if ($this->process !== null) { - $this->process->close(); + $this->deferred->resolve(); + + if ($this->process !== null && $this->process->isRunning()) { + $this->process->terminate(SIGKILL); } return $this; @@ -33,6 +62,7 @@ public function close() protected function writeln($line) { + if ($this->process === null) return; $this->process->stdin->write($line . PHP_EOL); } diff --git a/src/Zen/FileSelectionZen.php b/src/Zen/FileSelectionZen.php index 234f611..66c082d 100644 --- a/src/Zen/FileSelectionZen.php +++ b/src/Zen/FileSelectionZen.php @@ -3,8 +3,6 @@ namespace Clue\React\Zenity\Zen; use Clue\React\Zenity\Zen\BaseZen; -use React\Promise\Deferred; -use React\ChildProcess\Process; use SplFileInfo; class FileSelectionZen extends BaseZen @@ -12,10 +10,8 @@ class FileSelectionZen extends BaseZen private $multiple; private $separator; - public function __construct(Deferred $deferred, Process $process, $multiple, $separator) + public function __construct($multiple, $separator) { - parent::__construct($deferred, $process); - $this->multiple = $multiple; $this->separator = $separator; } diff --git a/src/Zen/FormsZen.php b/src/Zen/FormsZen.php index defa204..9d71172 100644 --- a/src/Zen/FormsZen.php +++ b/src/Zen/FormsZen.php @@ -3,17 +3,13 @@ namespace Clue\React\Zenity\Zen; use Clue\React\Zenity\Zen\BaseZen; -use React\Promise\Deferred; -use React\ChildProcess\Process; class FormsZen extends BaseZen { private $separator; - public function __construct(Deferred $deferred, Process $process, $separator) + public function __construct($separator) { - parent::__construct($deferred, $process); - $this->separator = $separator; } diff --git a/src/Zen/ListZen.php b/src/Zen/ListZen.php index d7b9cfa..9fd588f 100644 --- a/src/Zen/ListZen.php +++ b/src/Zen/ListZen.php @@ -3,18 +3,14 @@ namespace Clue\React\Zenity\Zen; use Clue\React\Zenity\Zen\BaseZen; -use React\Promise\Deferred; -use React\ChildProcess\Process; class ListZen extends BaseZen { private $single; private $separator; - public function __construct(Deferred $deferred, Process $process, $single, $separator) + public function __construct($single, $separator) { - parent::__construct($deferred, $process); - $this->single = $single; $this->separator = $separator; } diff --git a/src/Zen/PasswordZen.php b/src/Zen/PasswordZen.php index 585bf32..1ec8671 100644 --- a/src/Zen/PasswordZen.php +++ b/src/Zen/PasswordZen.php @@ -3,8 +3,6 @@ namespace Clue\React\Zenity\Zen; use Clue\React\Zenity\Zen\BaseZen; -use React\Promise\Deferred; -use React\ChildProcess\Process; class PasswordZen extends BaseZen { @@ -13,10 +11,8 @@ class PasswordZen extends BaseZen private $username; - public function __construct(Deferred $deferred, Process $process, $username) + public function __construct($username) { - parent::__construct($deferred, $process); - $this->username = $username; } diff --git a/src/Zen/ProgressZen.php b/src/Zen/ProgressZen.php index bb047f7..e45fe48 100644 --- a/src/Zen/ProgressZen.php +++ b/src/Zen/ProgressZen.php @@ -2,17 +2,14 @@ namespace Clue\React\Zenity\Zen; -use React\Promise\Deferred; -use React\ChildProcess\Process; +use Clue\React\Zenity\Zen\BaseZen; class ProgressZen extends BaseZen { private $percentage; - public function __construct(Deferred $deferred, Process $process, $percentage) + public function __construct($percentage) { - parent::__construct($deferred, $process); - $this->percentage = $percentage; } diff --git a/tests/Dialog/AbstractDialogTest.php b/tests/Dialog/AbstractDialogTest.php index aff339c..e461595 100644 --- a/tests/Dialog/AbstractDialogTest.php +++ b/tests/Dialog/AbstractDialogTest.php @@ -12,13 +12,12 @@ abstract protected function createDialog(); protected function createZen(AbstractDialog $dialog = null) { $process = $this->getMockBuilder('React\ChildProcess\Process')->disableOriginalConstructor()->getMock(); - $deferred = $this->getMock('React\Promise\Deferred'); if ($dialog === null) { $dialog = $this->createDialog(); } - return $dialog->createZen($deferred, $process); + return $dialog->createZen($process); } abstract protected function getType(); diff --git a/tests/Dialog/NotificationDialogTest.php b/tests/Dialog/NotificationDialogTest.php index 9d7d82d..b9bfd77 100644 --- a/tests/Dialog/NotificationDialogTest.php +++ b/tests/Dialog/NotificationDialogTest.php @@ -33,11 +33,7 @@ public function testUnsetBools() public function testZen() { - $dialog = new NotificationDialog(); - - $process = $this->getMockBuilder('React\ChildProcess\Process')->disableOriginalConstructor()->getMock(); - - $zen = $dialog->createZen($this->getMock('React\Promise\Deferred'), $process); + $zen = $this->createZen(); $this->assertInstanceOf('Clue\React\Zenity\Zen\NotificationZen', $zen); } diff --git a/tests/Dialog/ProgressDialogTest.php b/tests/Dialog/ProgressDialogTest.php index 13faca4..c90a56c 100644 --- a/tests/Dialog/ProgressDialogTest.php +++ b/tests/Dialog/ProgressDialogTest.php @@ -52,7 +52,7 @@ public function testZen() $process = $this->getMockBuilder('React\ChildProcess\Process')->disableOriginalConstructor()->getMock(); // TODO: assert writeline 50 - $zen = $dialog->createZen($this->getMock('React\Promise\Deferred'), $process); + $zen = $dialog->createZen(); $this->assertInstanceOf('Clue\React\Zenity\Zen\ProgressZen', $zen); } diff --git a/tests/Dialog/TextInfoDialogTest.php b/tests/Dialog/TextInfoDialogTest.php index 8045e13..68e555e 100644 --- a/tests/Dialog/TextInfoDialogTest.php +++ b/tests/Dialog/TextInfoDialogTest.php @@ -45,7 +45,7 @@ public function testZen() $process = $this->getMockBuilder('React\ChildProcess\Process')->disableOriginalConstructor()->getMock(); // TODO: assert writeline hello, world - $zen = $dialog->createZen($this->getMock('React\Promise\Deferred'), $process); + $zen = $dialog->createZen(); $this->assertInstanceOf('Clue\React\Zenity\Zen\TextInfoZen', $zen); } diff --git a/tests/Zen/BaseZenTest.php b/tests/Zen/BaseZenTest.php index 65eadaf..7a48406 100644 --- a/tests/Zen/BaseZenTest.php +++ b/tests/Zen/BaseZenTest.php @@ -1,11 +1,13 @@ process = $this->getMockBuilder('React\ChildProcess\Process')->disableOriginalConstructor()->getMock(); $this->process->stdin = $this->instream; - $this->deferred = $this->getMock('React\Promise\Deferred'); + $this->process->stdout = $this->getMock('React\Stream\ReadableStreamInterface'); } - public function testClose() + public function testClosingZenTerminatesProcess() { - //$this->instream->expects($this->once())->method('close'); + $this->process->expects($this->once())->method('isRunning')->will($this->returnValue(true)); + $this->process->expects($this->once())->method('terminate'); - $zen = new BaseZen($this->deferred, $this->process); + $zen = new BaseZen(); + $zen->go($this->process); $zen->close(); } - - public function testThen() - { - $this->deferred->expects($this->once())->method('then'); - - $zen = new BaseZen($this->deferred, $this->process); - - $zen->then(); - } } diff --git a/tests/Zen/CalendarZenTest.php b/tests/Zen/CalendarZenTest.php index 288a077..4f88212 100644 --- a/tests/Zen/CalendarZenTest.php +++ b/tests/Zen/CalendarZenTest.php @@ -6,7 +6,7 @@ class CalendarZenTest extends BaseZenTest { public function testParsingValues() { - $zen = new CalendarZen($this->deferred, $this->process); + $zen = new CalendarZen(); $this->assertEquals(new DateTime('2014-08-02'), $zen->parseValue('2014-08-02')); } diff --git a/tests/Zen/ColorSelectionZenTest.php b/tests/Zen/ColorSelectionZenTest.php index 3f9d149..8f6b81f 100644 --- a/tests/Zen/ColorSelectionZenTest.php +++ b/tests/Zen/ColorSelectionZenTest.php @@ -6,7 +6,7 @@ class ColorSelectionZenTest extends BaseZenTest { public function testParsingValues() { - $zen = new ColorSelectionZen($this->deferred, $this->process); + $zen = new ColorSelectionZen(); $this->assertEquals('#123456', $zen->parseValue('#121234345656')); } diff --git a/tests/Zen/FunctionalBaseZenTest.php b/tests/Zen/FunctionalBaseZenTest.php new file mode 100644 index 0000000..531ab1b --- /dev/null +++ b/tests/Zen/FunctionalBaseZenTest.php @@ -0,0 +1,85 @@ +loop = Factory::create(); + } + + public function testZenResolvesWithProcessOutput() + { + $process = new Process('echo okay'); + $process->start($this->loop); + + $zen = new BaseZen(); + $zen->go($process); + + $this->loop->run(); + + $zen->then($this->expectCallableOnceWith('okay')); + } + + public function testZenResolvesWithTrueWhenProcessHasNoOutput() + { + $process = new Process('true'); + $process->start($this->loop); + + $zen = new BaseZen(); + $zen->go($process); + + $this->loop->run(); + + $zen->then($this->expectCallableOnceWith(true)); + } + + public function testZenRejectsWhenProcessReturnsError() + { + $process = new Process('echo nevermind && false'); + $process->start($this->loop); + + $zen = new BaseZen(); + $zen->go($process); + + $this->loop->run(); + + $zen->then(null, $this->expectCallableOnceWith(1)); + } + + public function testClosingZenResolvesWithOutputSoFar() + { + $process = new Process('echo okay && cat && echo not'); + $process->start($this->loop); + + $zen = new BaseZen(); + $zen->go($process); + + $this->loop->addTimer(0.1, function() use ($zen) { + $zen->close(); + }); + + $this->loop->run(); + + $zen->then($this->expectCallableOnceWith('okay')); + } + + public function testTerminatingProcessReturnsError() + { + $process = new Process('echo nevermind && cat'); + $process->start($this->loop); + + $zen = new BaseZen(); + $zen->go($process); + + $this->loop->addTimer(0.1, function() use ($process) { + $process->terminate(SIGKILL); + }); + + $this->loop->run(); + + $zen->then(null, $this->expectCallableOnce()); + } +} diff --git a/tests/Zen/NotificationZenTest.php b/tests/Zen/NotificationZenTest.php index 85af063..1d5b400 100644 --- a/tests/Zen/NotificationZenTest.php +++ b/tests/Zen/NotificationZenTest.php @@ -6,7 +6,8 @@ class NotificationZenTest extends BaseZenTest { public function testCommands() { - $zen = new NotificationZen($this->deferred, $this->process); + $zen = new NotificationZen(); + $zen->go($this->process); $this->assertSame($zen, $zen->setIcon('icon')); $this->assertSame($zen, $zen->setVisible(true)); diff --git a/tests/Zen/ProgressZenTest.php b/tests/Zen/ProgressZenTest.php index 909d83a..2d61e77 100644 --- a/tests/Zen/ProgressZenTest.php +++ b/tests/Zen/ProgressZenTest.php @@ -6,7 +6,8 @@ class ProgressZenTest extends BaseZenTest { public function testPercentage() { - $zen = new ProgressZen($this->deferred, $this->process, 0); + $zen = new ProgressZen(0); + $zen->go($this->process); $this->assertEquals(0, $zen->getPercentage()); diff --git a/tests/Zen/TextInfoZenTest.php b/tests/Zen/TextInfoZenTest.php index f893a47..e1e938a 100644 --- a/tests/Zen/TextInfoZenTest.php +++ b/tests/Zen/TextInfoZenTest.php @@ -6,7 +6,8 @@ class TextInfoZenTest extends BaseZenTest { public function testAddLine() { - $zen = new TextInfoZen($this->deferred, $this->process); + $zen = new TextInfoZen(); + $zen->go($this->process); $this->assertSame($zen, $zen->addLine('hello')); $this->assertSame($zen, $zen->addLine('world')); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 7c67587..8117eec 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -8,38 +8,31 @@ protected function expectCallableOnce() { $mock = $this->createCallableMock(); - - if (func_num_args() > 0) { - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->equalTo(func_get_arg(0))); - } else { - $mock - ->expects($this->once()) - ->method('__invoke'); - } + $mock + ->expects($this->once()) + ->method('__invoke'); return $mock; } - protected function expectCallableNever() + protected function expectCallableOnceWith($value) { $mock = $this->createCallableMock(); + $mock - ->expects($this->never()) - ->method('__invoke'); + ->expects($this->once()) + ->method('__invoke') + ->with($this->equalTo($value)); return $mock; } - protected function expectCallableOnceParameter($type) + protected function expectCallableNever() { $mock = $this->createCallableMock(); $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->isInstanceOf($type)); + ->expects($this->never()) + ->method('__invoke'); return $mock; }