election1 = new Election; $this->candidate1 = $this->election1->addCandidate('candidate1'); $this->candidate2 = $this->election1->addCandidate('candidate2'); $this->candidate3 = $this->election1->addCandidate('candidate3'); $this->election1->addVote($this->vote1 = new Vote([$this->candidate1, $this->candidate2, $this->candidate3])); $this->election1->addVote($this->vote2 = new Vote([$this->candidate2, $this->candidate3, $this->candidate1])); $this->election1->addVote($this->vote3 = new Vote([$this->candidate3, $this->candidate1, $this->candidate2])); $this->election1->addVote($this->vote4 = new Vote([$this->candidate1, $this->candidate2, $this->candidate3])); $this->election2 = new Election; } public function testRemoveVotes(): never { $this->expectException(VoteException::class); $this->expectExceptionMessage('Problem handling vote: cannot remove vote, is not registered in this election'); self::assertTrue($this->election1->removeVote($this->vote2)); self::assertCount(3, $this->election1->getVotesList()); $badRemoveVote = new Vote('A'); $this->election1->removeVote($badRemoveVote); } public function testRemoveVotesByTags(): void { $this->vote1->addtags('tag1,tag2,tag3'); $this->vote2->addtags('tag3,tag4'); $this->vote3->addtags('tag3,tag4,tag5'); $this->vote4->addtags('tag1,tag4'); self::assertCount(3, $r = $this->election1->removeVotesByTags(['tag1', 'tag5'])); self::assertSame([$this->vote1, $this->vote3, $this->vote4], $r); self::assertSame([1 => $this->vote2], $this->election1->getVotesList()); $this->setUp(); $this->vote1->addtags('tag1,tag2,tag3'); $this->vote2->addtags('tag3,tag4'); $this->vote3->addtags('tag3,tag4,tag5'); $this->vote4->addtags('tag1,tag4'); self::assertCount(1, $r = $this->election1->removeVotesByTags('tag1,tag5', false)); self::assertSame([$this->vote2], $r); self::assertSame([0 => $this->vote1, 2=> $this->vote3, 3 => $this->vote4], $this->election1->getVotesList()); } public function testTagsFilter(): void { $this->vote1->addtags('tag1,tag2,tag3'); $this->vote2->addtags('tag3,tag4'); $this->vote3->addtags('tag3,tag4,tag5'); $this->vote4->addtags('tag1,tag4'); self::assertSame($this->election1->getVotesList('tag1,tag2', true), [0=>$this->vote1, 3=>$this->vote4]); self::assertSame($this->election1->countVotes('tag1,tag2', true), 2); self::assertSame($this->election1->getVotesList('tag1,tag2', false), [1=>$this->vote2, 2=>$this->vote3]); self::assertSame($this->election1->countVotes('tag1,tag2', false), 2); $resultGlobal = $this->election1->getResult('Schulze'); $resultFilter1 = $this->election1->getResult('Schulze', ['tags' => 'tag1', 'withTag' => true]); $resultFilter2 = $this->election1->getResult('Schulze', ['tags' => 'tag1', 'withTag' => false]); self::assertNotSame($resultGlobal, $resultFilter1); self::assertNotSame($resultGlobal, $resultFilter2); self::assertNotSame($resultFilter1, $resultFilter2); } public function testParseCandidates(): void { self::assertCount( 4, $this->election2->parseCandidates('Bruckner; Mahler ; Debussy Bibendum') ); self::assertSame( ['Bruckner', 'Mahler', 'Debussy', 'Bibendum'], $this->election2->getCandidatesListAsString() ); } public function testgetCandidateObjectFromName(): void { self::assertSame($this->candidate1, $this->election1->getCandidateObjectFromName('candidate1')); self::assertNull($this->election1->getCandidateObjectFromName('candidate42')); } public function testParseError(): never { $this->expectException(VoteInvalidFormatException::class); $this->expectExceptionMessage("The format of the vote is invalid: the value 'text' is not an integer."); $this->election1->parseVotes('candidate1>candidate2 * text'); } /** * @preserveGlobalState disabled * @backupStaticAttributes disabled * @runInSeparateProcess */ public function testMaxParseIteration1(): never { $this->expectException(VoteMaxNumberReachedException::class); $this->expectExceptionMessage('The maximal number of votes for the method is reached: 42'); self::assertSame(42, Election::setMaxParseIteration(42)); self::assertCount(42, $this->election1->parseVotes('candidate1>candidate2 * 42')); self::assertCount(42, $this->election1->parseVotes('candidate1>candidate2 * 42')); self::assertNull(Election::setMaxParseIteration(null)); self::assertCount(43, $this->election1->parseVotes('candidate1>candidate2 * 43')); self::assertSame(42, Election::setMaxParseIteration(42)); $this->election1->parseVotes('candidate1>candidate2 * 43'); } /** * @preserveGlobalState disabled * @backupStaticAttributes disabled * @runInSeparateProcess */ public function testMaxParseIteration2(): never { $this->expectException(VoteMaxNumberReachedException::class); $this->expectExceptionMessage('The maximal number of votes for the method is reached: 42'); self::assertSame(42, Election::setMaxParseIteration(42)); self::assertSame(42, $this->election1->parseVotes(' candidate1>candidate2 * 21 candidate1>candidate2 * 21 candidate1>candidate2 * 21 ')); } /** * @preserveGlobalState disabled * @backupStaticAttributes disabled * @runInSeparateProcess */ public function testMaxParseIteration3(): never { $this->expectException(VoteMaxNumberReachedException::class); $this->expectExceptionMessage('The maximal number of votes for the method is reached: 2'); self::assertSame(2, Election::setMaxParseIteration(2)); self::assertSame([0=>'candidate1', 1=>'candidate2'], $this->election2->parseCandidates('candidate1;candidate2')); self::assertSame([0=>'candidate3', 1=>'candidate4'], $this->election2->parseCandidates('candidate3;candidate4')); self::assertNull(Election::setMaxParseIteration(null)); self::assertSame([0=>'candidate5', 1=>'candidate6', 2=>'candidate7'], $this->election2->parseCandidates('candidate5;candidate6;candidate7')); self::assertSame(2, Election::setMaxParseIteration(2)); $this->election2->parseCandidates('candidate8;candidate9;candidate10'); } /** * @preserveGlobalState disabled * @backupStaticAttributes disabled * @runInSeparateProcess */ public function testMaxVoteNumber(): never { $this->expectException(VoteMaxNumberReachedException::class); $this->expectExceptionMessage('The maximal number of votes for the method is reached'); $election = new Election; self::assertCount(3, $election->parseCandidates('candidate1;candidate2;candidate3')); self::assertSame(42, Election::setMaxVoteNumber(42)); self::assertSame(21, $election->parseVotes('candidate1>candidate2 * 21')); try { $election->parseVotes('candidate1>candidate2 * 42'); self::assertTrue(false); } catch (VoteMaxNumberReachedException $e) { $this->assertEquals('The maximal number of votes for the method is reached', $e->getMessage()); } self::assertSame(21, $election->countVotes()); $election->parseVotes('candidate1 * 21'); self::assertSame(42, $election->countVotes()); self::assertNull(Election::setMaxVoteNumber(null)); $election->addVote('candidate3'); self::assertSame(42, Election::setMaxVoteNumber(42)); try { $election->addVote('candidate3'); } catch (VoteMaxNumberReachedException $e) { $reserveException = $e; } self::assertNull(Election::setMaxVoteNumber(null)); throw $reserveException; } public function testGetVotesListAsString(): void { $this->election1 = new Election; $this->election1->addCandidate('C'); $this->election1->addCandidate('B'); $this->election1->addCandidate('D'); $this->election1->addCandidate('E'); $this->election1->addCandidate('A'); $this->election1->parseVotes(' D * 6 A * 6 E = A = B *3 A > C = B > E * 5 Y > Z '); self::assertSame( "A > B = C = D = E * 6\n". "D > A = B = C = E * 6\n". "A > B = C > E > D * 5\n". "A = B = E > C = D * 3\n". 'A = B = C = D = E * 1', $this->election1->getVotesListAsString() ); $this->election1->setImplicitRanking(false); self::assertSame( "A * 6\n". "D * 6\n". "A > B = C > E * 5\n". "A = B = E * 3\n". '/EMPTY_RANKING/ * 1', $this->election1->getVotesListAsString() ); self::assertSame( <<<'VOTES' A * 6 D * 6 A > B = C > E * 5 A = B = E * 3 Y > Z * 1 VOTES, $this->election1->getVotesListAsString(false) ); } public function testEmptyRankingExport(): void { $this->election2->parseCandidates('A;B;C'); $this->election2->setImplicitRanking(false); $this->election2->addVote(new Vote('')); $this->election2->addVote(new Vote('D>E')); self::assertSame('/EMPTY_RANKING/ * 2', $this->election2->getVotesListAsString(true)); self::assertSame('/EMPTY_RANKING/ * 1'. "\n" .'D > E * 1', $this->election2->getVotesListAsString(false)); self::assertSame( $cvotes_explicit_without_context = <<<'CVOTES' #/Candidates: A ; B ; C #/Implicit Ranking: false #/Weight Allowed: false /EMPTY_RANKING/ * 1 D > E * 1 CVOTES, CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $this->election2, includeNumberOfSeats: false, aggregateVotes: true, inContext: false) ); self::assertSame( str_replace(' * 1', '', $cvotes_explicit_without_context), CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $this->election2, includeNumberOfSeats: false, aggregateVotes: false, inContext: false) ); self::assertSame( <<<'CVOTES' #/Candidates: A ; B ; C #/Implicit Ranking: false #/Weight Allowed: false /EMPTY_RANKING/ * 2 CVOTES, CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $this->election2, includeNumberOfSeats: false, aggregateVotes: true, inContext: true) ); self::assertSame( <<<'CVOTES' #/Candidates: A ; B ; C #/Implicit Ranking: false #/Weight Allowed: false /EMPTY_RANKING/ /EMPTY_RANKING/ CVOTES, CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $this->election2, includeNumberOfSeats: false, aggregateVotes: false, inContext: true) ); $this->election2->setImplicitRanking(true); self::assertSame( <<<'CVOTES' #/Candidates: A ; B ; C #/Implicit Ranking: true #/Weight Allowed: false A = B = C * 2 CVOTES, CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $this->election2, includeNumberOfSeats: false, aggregateVotes: true, inContext: true) ); self::assertSame( <<<'CVOTES' #/Candidates: A ; B ; C #/Implicit Ranking: true #/Weight Allowed: false A = B = C A = B = C CVOTES, CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $this->election2, includeNumberOfSeats: false, aggregateVotes: false, inContext: true) ); $this->election2 = new Election; $this->election2->parseCandidates('A;B;C;D'); $this->election2->setImplicitRanking(true); $this->election2->addVote(new Vote('A>B')); self::assertSame( <<<'CVOTES' #/Candidates: A ; B ; C ; D #/Implicit Ranking: true #/Weight Allowed: false A > B > C = D * 1 CVOTES, CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $this->election2, includeNumberOfSeats: false, aggregateVotes: true, inContext: true) ); self::assertSame( $cvotes_implicit_without_context = <<<'CVOTES' #/Candidates: A ; B ; C ; D #/Implicit Ranking: true #/Weight Allowed: false A > B * 1 CVOTES, CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $this->election2, includeNumberOfSeats: false, aggregateVotes: true, inContext: false) ); self::assertSame( str_replace(' * 1', '', $cvotes_implicit_without_context), CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $this->election2, includeNumberOfSeats: false, aggregateVotes: false, inContext: false) ); } public function testParseVoteCandidateCoherence(): void { $this->election1 = new Election; $cA = $this->election1->addCandidate('A'); $cB = $this->election1->addCandidate('B'); $cC = $this->election1->addCandidate('C'); self::assertSame(2, $this->election1->parseVotes(' A>B>C * 2 ')); $votes = $this->election1->getVotesList(); foreach ($votes as $vote) { $ranking = $vote->getRanking(); self::assertSame($cA, $ranking[1][0]); self::assertSame($cB, $ranking[2][0]); self::assertSame($cC, $ranking[3][0]); } } public function testParseVotesInvalidPath(): void { $this->expectException(FileDoesNotExistException::class); $this->expectExceptionMessageMatches('/bad_file.txt$/'); $this->election1 = new Election; $this->election1->addCandidate('A'); $this->election1->addCandidate('B'); $this->election1->parseVotes('bad_file.txt', true); } public function testParseVotesWithoutFail(): void { $this->election1 = new Election; $this->election1->addCandidate('A'); $this->election1->addCandidate('B'); $this->election1->addCandidate('C'); self::assertSame(2, $this->election1->parseVotesWithoutFail(' A > B > C A > B > C * 4;tag1 || A > B > C*4 #Coucou A < B < C * 10 D <> B A > B > C ')); self::assertSame(10, $this->election1->countVotes()); self::assertSame(2, $this->election1->parseVotesWithoutFail(__DIR__.'/../LargeElectionData/smallVote1.votes', true)); self::assertSame(20, $this->election1->countVotes()); self::assertSame(2, $this->election1->parseVotesWithoutFail(new \SplFileObject(__DIR__.'/../LargeElectionData/smallVote1.votes'), true)); self::assertSame(30, $this->election1->countVotes()); self::assertSame(2, $this->election1->parseVotesWithoutFail(new \SplFileInfo(__DIR__.'/../LargeElectionData/smallVote1.votes'), true)); self::assertSame(40, $this->election1->countVotes()); } public function testParseVotesWithoutFailInvalidPath(): void { $this->expectException(FileDoesNotExistException::class); $this->expectExceptionMessageMatches('/bad_file.txt$/'); $this->election1 = new Election; $this->election1->addCandidate('A'); $this->election1->addCandidate('B'); $this->election1->parseVotesWithoutFail('bad_file.txt', true); } public function testVoteWeight(): void { $election = new Election; $election->addCandidate('A'); $election->addCandidate('B'); $election->addCandidate('C'); $election->addCandidate('D'); $election->parseVotes(' A > C > D * 6 B > A > D * 1 C > B > D * 3 D > B > A * 3 '); $voteWithWeight = $election->addVote('D > C > B'); $voteWithWeight->setWeight(2); self::assertSame( 14, $election->sumVotesWeight() ); self::assertSame( 'D > C > B ^2', (string) $voteWithWeight ); self::assertSame( 'D > C > B > A', $voteWithWeight->getSimpleRanking($election) ); self::assertNotSame( 'A = D > C > B', $election->getResult('Schulze Winning')->getResultAsString() ); $election->allowsVoteWeight(true); self::assertSame( 15, $election->sumVotesWeight() ); self::assertSame( 'D > C > B > A ^2', $voteWithWeight->getSimpleRanking($election) ); self::assertSame( 'A = D > C > B', $election->getResult('Schulze Winning')->getResultAsString() ); $election->allowsVoteWeight(false); self::assertSame( 14, $election->sumVotesWeight() ); self::assertNotSame( 'A = D > C > B', $election->getResult('Schulze Winning')->getResultAsString() ); $election->allowsVoteWeight(!$election->isVoteWeightAllowed()); $election->removeVote($voteWithWeight); self::assertSame( 13, $election->sumVotesWeight() ); $election->parseVotes(' D > C > B ^2 * 1 '); self::assertSame( 15, $election->sumVotesWeight() ); self::assertSame( 'A = D > C > B', $election->getResult('Schulze Winning')->getResultAsString() ); $election->addVote('D > C > B'); self::assertSame( <<<'VOTES' A > C > D > B * 6 C > B > D > A * 3 D > B > A > C * 3 D > C > B > A ^2 * 1 B > A > D > C * 1 D > C > B > A * 1 VOTES, $election->getVotesListAsString() ); } public function testaddVotesFromJson(): never { $this->expectException(\JsonException::class); $election = new Election; $election->addCandidate('A'); $election->addCandidate('B'); $election->addCandidate('C'); $votes = []; $votes[]['vote'] = 'B>C>A'; $votes[]['vote'] = new \stdClass; // Invalid Vote $votes[]['vote'] = ['C', 'B', 'A']; self::assertSame(2, $election->addVotesFromJson(json_encode($votes))); self::assertSame( <<<'VOTES' B > C > A * 1 C > B > A * 1 VOTES, $election->getVotesListAsString() ); $votes = []; $votes[0]['vote'] = 'A>B>C'; $votes[0]['multi'] = 5; $votes[0]['tag'] = 'tag1'; $votes[0]['weight'] = '42'; $election->addVotesFromJson(json_encode($votes)); $election->allowsVoteWeight(true); self::assertSame( <<<'VOTES' A > B > C ^42 * 5 B > C > A * 1 C > B > A * 1 VOTES, $election->getVotesListAsString() ); self::assertSame(5, $election->countVotes('tag1')); $election->addVotesFromJson(json_encode($votes).'{42'); } public function testaddCandidatesFromJson(): never { $this->expectException(CandidateExistsException::class); $this->expectExceptionMessage('This candidate already exists: candidate2'); $election = new Election; $candidates = ['candidate1 ', 'candidate2']; $election->addCandidatesFromJson(json_encode($candidates)); self::assertSame(2, $election->countCandidates()); self::assertEquals(['candidate1', 'candidate2'], $election->getCandidatesListAsString()); $election->addCandidatesFromJson(json_encode(['candidate2'])); } public function testaddCandidatesFromInvalidJson(): never { $this->expectException(\JsonException::class); $election = new Election; $election->addCandidatesFromJson(json_encode(['candidate3']).'{42'); } public function testaddVotesFromJsonWithInvalidJson(): void { $errors = ['42', 'true', 'false', 'null', '', ' ', json_encode(new \stdClass)]; foreach ($errors as $oneError) { try { $this->election2->addVotesFromJson($oneError); // Else fail $this->fail("{$oneError} is not a valid Json for PHP"); } catch (\JsonException) { } } self::assertEmpty($this->election2->getVotesList()); } public function testCachingResult(): void { $election = new Election; $election->addCandidate('A'); $election->addCandidate('B'); $election->addCandidate('C'); $election->addCandidate('D'); $election->addVote($vote1 = new Vote('A > C > D')); $result1 = $election->getResult('Schulze'); self::assertSame($result1, $election->getResult('Schulze')); } public function testElectionSerializing(): void { $election = new Election; $election->addCandidate('A'); $election->addCandidate('B'); $election->addCandidate('C'); $election->addCandidate('D'); $election->addVote($vote1 = new Vote('A > C > D')); $result1 = $election->getResult('Schulze'); $election = serialize($election); // file_put_contents("Tests/src/ElectionData/serialized_election_v3.2.0.txt", $election); # For next test $election = unserialize($election); self::assertNotSame($result1, $election->getResult('Schulze')); self::assertSame($result1->getResultAsString(), $election->getResult('Schulze')->getResultAsString()); self::assertNotSame($vote1, $election->getVotesList()[0]); self::assertSame($vote1->getSimpleRanking(), $election->getVotesList()[0]->getSimpleRanking()); self::assertTrue($election->getVotesList()[0]->haveLink($election)); self::assertFalse($vote1->haveLink($election)); } public function testElectionUnserializing(): void { $this->expectException(ElectionObjectVersionMismatchException::class); $this->expectExceptionMessage( "Version mismatch: The election object has version '3.2' " . "which is different from the current class version '".Condorcet::getVersion(true)."'" ); unserialize( file_get_contents('Tests/src/ElectionData/serialized_election_v3.2.0.txt') ); } public function testCloneElection(): void { $this->election1->computeResult(); $cloneElection = clone $this->election1; self::assertNotSame($this->election1->getVotesManager(), $cloneElection->getVotesManager()); self::assertSame($this->election1->getVotesList(), $cloneElection->getVotesList()); self::assertSame($this->election1->getCandidatesList(), $cloneElection->getCandidatesList()); self::assertNotSame($this->election1->getPairwise(), $cloneElection->getPairwise()); self::assertEquals($this->election1->getExplicitPairwise(), $cloneElection->getExplicitPairwise()); self::assertNotSame($this->election1->getTimerManager(), $cloneElection->getTimerManager()); self::assertSame($this->election1->getVotesList()[0], $cloneElection->getVotesList()[0]); self::assertTrue($cloneElection->getVotesList()[0]->haveLink($this->election1)); self::assertTrue($cloneElection->getVotesList()[0]->haveLink($cloneElection)); } public function testPairwiseArrayAccess(): void { $this->election1->computeResult(); self::assertTrue($this->election1->getPairwise()->offsetExists(1)); } public function testGetCandidateObjectFromKey(): void { self::assertSame($this->candidate2, $this->election1->getCandidateObjectFromKey(1)); self::assertNull($this->election1->getCandidateObjectFromKey(42)); } public function testElectionState1(): never { $this->expectException(VotingHasStartedException::class); $this->expectExceptionMessage("The voting has started: cannot add 'candidate4'"); $this->election1->addCandidate('candidate4'); } public function testElectionState2(): never { $this->expectException(VotingHasStartedException::class); $this->expectExceptionMessage('The voting has started'); $this->election1->removeCandidates('candidate4'); } public function testElectionState3(): never { $this->expectException(NoCandidatesException::class); $this->expectExceptionMessage('You need to specify one or more candidates before voting'); $election = new Election; $election->setStateTovote(); } public function testElectionState4(): never { $this->expectException(ResultRequestedWithoutVotesException::class); $this->expectExceptionMessage('The result cannot be requested without votes'); $election = new Election; $election->getResult(); } public function testElectionState5(): void { $this->election1->getResult(); self::assertTrue($this->election1->setStateTovote()); self::assertSame(ElectionState::VOTES_REGISTRATION, $this->election1->getState()); } public function testAddSameVote(): never { $this->expectException(VoteException::class); $this->expectExceptionMessage('Problem handling vote: seats are already registered'); $this->election1->addVote($this->vote1); } public function testDestroy(): void { $election = new Election; $election->addCandidate('candidate1'); $election->addCandidate('candidate2'); $election->addCandidate('candidate3'); $election->addVote('candidate1>candidate2'); $weakref = \WeakReference::create($election); // PHP circular reference can bug // \debug_zval_dump($election); unset($election); self::assertNull($weakref->get()); } public function testRemoveCandidate(): never { $this->expectException(CandidateDoesNotExistException::class); $this->expectExceptionMessage('This candidate does not exist: B'); $election = new Election; $election->addCandidate('candidate1'); $badCandidate = new Candidate('B'); $election->removeCandidates($badCandidate); } /** * @dataProvider MethodsListProvider */ public function testRemoveCandidateResult(string $method): void { $votes = ' Memphis * 4 Nashville * 3 Chattanooga * 2 Knoxville * 1'; // Ref $electionRef = new Election; $electionRef->addCandidate('Memphis'); $electionRef->addCandidate('Nashville'); $electionRef->addCandidate('Knoxville'); $electionRef->addCandidate('Chattanooga'); $electionRef->parseVotes($votes); // Test $electionTest = new Election; $electionTest->addCandidate('Memphis'); $electionTest->addCandidate('BadCandidate'); $electionTest->addCandidate('Nashville'); $electionTest->addCandidate('Knoxville'); $electionTest->addCandidate('Chattanooga'); $electionTest->removeCandidates('BadCandidate'); $electionTest->parseVotes($votes); self::assertSame( $electionRef->getResult($method)->getResultAsArray(true), $electionTest->getResult($method)->getResultAsArray(true) ); } public function MethodsListProvider(): array { $r = []; foreach (Condorcet::getAuthMethods() as $method) { $r[] = [$method]; } return $r; } public function testAmbiguousCandidatesOnElectionSide(): never { $this->expectException(VoteInvalidFormatException::class); $this->expectExceptionMessage('The format of the vote is invalid'); $vote = new Vote('candidate1>candidate2'); $election1 = new Election; $election2 = new Election; $election1->addCandidate(new Candidate('candidate2')); $election2->addCandidate(new Candidate('candidate1')); $election1->addVote($vote); $election2->addVote($vote); } public function testAmbiguousCandidatesOnVoteSide(): never { $this->expectException(VoteInvalidFormatException::class); $this->expectExceptionMessage('The format of the vote is invalid: vote does not match candidate in this election'); $election1 = new Election; $election2 = new Election; $election1->addCandidate(new Candidate('candidate1')); $election2->addCandidate(new Candidate('candidate2')); $candidate3 = new Candidate('candidate3'); $election1->addCandidate($candidate3); $election2->addCandidate($candidate3); $vote = new Vote('candidate3'); $election1->addVote($vote); $election2->addVote($vote); $election1->getResult(); $election2->getResult(); try { $vote->setRanking('candidate1>candidate2>candidate3'); } catch (\Exception $e) { } self::assertEmpty($election1->debugGetCalculator()); self::assertEmpty($election2->debugGetCalculator()); throw $e; } public function testInvalidSeats(): never { $this->expectException(NoSeatsException::class); $this->expectExceptionMessage('No seats defined'); $this->election1->setNumberOfSeats(0); } public function testSeats(): void { self::assertSame(100, $this->election1->getNumberOfSeats()); $this->election1->setNumberOfSeats(5); self::assertSame(5, $this->election1->getNumberOfSeats()); } }