run(); (new Process(['rm', '-rf', $repo]))->run(); return $process; } it('counts the releases ahead without taking the agent down with it', function () { // The exact shape that failed: the newest tag is ahead, and the tags after // it in the list are not — so the loop's final iteration is a false one. $process = runRelease( 'release_tags_ahead 1.2.0', ['v1.1.0', 'v1.1.1', 'v1.2.0', 'v1.2.1'], ); expect($process->getExitCode())->toBe(0) ->and(trim($process->getOutput()))->toBe('1'); }); it('survives a run where no tag at all is ahead', function () { // Every iteration false, including the last. This is the ordinary state of // an up-to-date server, which is to say: most ticks, on most machines. $process = runRelease( 'release_tags_ahead 1.2.1', ['v1.1.0', 'v1.2.0', 'v1.2.1'], ); expect($process->getExitCode())->toBe(0) ->and(trim($process->getOutput()))->toBe('0'); }); it('survives a repository with no tags whatsoever', function () { $process = runRelease('release_tags_ahead 1.0.0'); expect($process->getExitCode())->toBe(0) ->and(trim($process->getOutput()))->toBe('0'); }); it('counts several releases ahead', function () { $process = runRelease( 'release_tags_ahead 1.1.0', ['v1.1.0', 'v1.1.1', 'v1.2.0', 'v1.2.1'], ); expect($process->getExitCode())->toBe(0) ->and(trim($process->getOutput()))->toBe('3'); }); it('picks the newest tag out of a long list without dying on a broken pipe', function () { // The second instance of the same trap. `git tag … | head -1` makes head // exit after one line, git takes SIGPIPE, and pipefail ends the caller. // Few tags hide it — git finishes writing before head leaves — so it needs // enough of them to actually fill a pipe buffer. $tags = []; for ($i = 1; $i <= 400; $i++) { $tags[] = 'v1.'.$i.'.0'; } $process = runRelease('release_newest_tag', $tags); expect($process->getExitCode())->toBe(0) ->and(trim($process->getOutput()))->toBe('v1.400.0'); }); it('answers with nothing, and cleanly, when there are no releases yet', function () { $process = runRelease('release_newest_tag; echo "[$?]"'); expect($process->getExitCode())->toBe(0) ->and(trim($process->getOutput()))->toBe('[0]'); }); it('orders by version, not alphabetically', function () { // v1.10.0 is newer than v1.9.0 and sorts earlier as a string. Getting this // wrong means a server sitting on 1.9.0 is told it is current. $process = runRelease( 'release_tags_ahead 1.9.0', ['v1.9.0', 'v1.10.0'], ); expect($process->getExitCode())->toBe(0) ->and(trim($process->getOutput()))->toBe('1'); }); it('answers the pairwise comparison the same way, and exits cleanly either way', function () { $cases = [ ['1.2.1', '1.2.0', 'ja'], ['1.2.0', '1.2.1', 'nein'], ['1.2.0', '1.2.0', 'nein'], ['1.10.0', '1.9.0', 'ja'], ['2.0.0', '1.99.99', 'ja'], ]; foreach ($cases as [$a, $b, $expected]) { $process = runRelease("if release_version_gt {$a} {$b}; then echo ja; else echo nein; fi"); expect($process->getExitCode())->toBe(0) ->and(trim($process->getOutput()))->toBe($expected, "{$a} > {$b}"); } }); it('stops at the ceiling instead of taking the newest tag', function () { // Der Kern des Festnagelns: v1.8.1 existiert, darf aber nicht genommen // werden, weil die Decke auf v1.8.0 steht. $p = runRelease('release_newest_tag v1.8.0', ['v1.7.3', 'v1.8.0', 'v1.8.1']); expect($p->getExitCode())->toBe(0) ->and(trim($p->getOutput()))->toBe('v1.8.0'); }); it('takes the newest tag when no ceiling is given', function () { // Das bisherige Verhalten muss unveraendert bleiben — ohne Decke aendert // sich nichts an einem Server, der nie festgenagelt wurde. $p = runRelease('release_newest_tag', ['v1.7.3', 'v1.8.0', 'v1.8.1']); expect($p->getExitCode())->toBe(0) ->and(trim($p->getOutput()))->toBe('v1.8.1'); }); it('answers nothing when every tag is above the ceiling', function () { $p = runRelease('release_newest_tag v1.0.0', ['v1.7.3', 'v1.8.1']); expect($p->getExitCode())->toBe(0) ->and(trim($p->getOutput()))->toBe(''); }); it('counts only the tags up to the ceiling as ahead', function () { // Ausgeliefert 1.7.3, Decke v1.8.0, vorhanden bis v1.8.2: genau EINE // Aktualisierung ist verfuegbar, nicht drei. $p = runRelease('release_tags_ahead 1.7.3 v1.8.0', ['v1.7.3', 'v1.8.0', 'v1.8.1', 'v1.8.2']); expect($p->getExitCode())->toBe(0) ->and(trim($p->getOutput()))->toBe('1'); }); it('counts nothing ahead when the ceiling equals what is deployed', function () { // Der haeufigste Fall: "hier einfrieren". Muss sich wie "aktuell" // rechnen, sonst bietet die Konsole etwas an, das nie installiert wird. $p = runRelease('release_tags_ahead 1.8.0 v1.8.0', ['v1.8.0', 'v1.8.1', 'v1.8.2']); expect($p->getExitCode())->toBe(0) ->and(trim($p->getOutput()))->toBe('0'); }); it('knows whether a tag really exists', function () { // Form und Existenz sind zwei Fragen. Der Release-Prozess loescht einen // falschen Tag und ueberspringt die Nummer — eine Decke kann also ohne // Zutun ihres Setzers ins Leere zeigen. $p = runRelease('release_tag_exists v1.8.0 && echo ja; release_tag_exists v9.9.9 || echo nein', ['v1.8.0']); expect($p->getExitCode())->toBe(0) ->and(trim($p->getOutput()))->toBe("ja\nnein"); }); it('offers the deployed version itself as a ceiling, newest first', function () { // Die ausgelieferte Version steht MIT in der Liste: "einfrieren, nichts // Neues nehmen" ist das, was neun von zehn Servern brauchen. $p = runRelease('release_tags_from 1.8.0', ['v1.7.3', 'v1.8.0', 'v1.8.1']); expect($p->getExitCode())->toBe(0) ->and(trim($p->getOutput()))->toBe("v1.8.1\nv1.8.0"); }); it('caps the offered list', function () { $p = runRelease('release_tags_from 1.0.0 2', ['v1.0.0', 'v1.1.0', 'v1.2.0', 'v1.3.0']); expect($p->getExitCode())->toBe(0) ->and(trim($p->getOutput()))->toBe("v1.3.0\nv1.2.0"); });