405 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			405 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| declare(strict_types=1);
 | |
| 
 | |
| namespace CondorcetPHP\Condorcet\Tests\Console\Commands;
 | |
| 
 | |
| use CondorcetPHP\Condorcet\Console\Commands\ElectionCommand;
 | |
| use CondorcetPHP\Condorcet\Throwable\{CandidateExistsException, ResultRequestedWithoutVotesException};
 | |
| use PHPUnit\Framework\TestCase;
 | |
| use CondorcetPHP\Condorcet\Console\CondorcetApplication;
 | |
| use CondorcetPHP\Condorcet\Console\Style\CondorcetStyle;
 | |
| use Symfony\Component\Console\Output\OutputInterface;
 | |
| use Symfony\Component\Console\Tester\CommandTester;
 | |
| 
 | |
| class ElectionCommandTest extends TestCase
 | |
| {
 | |
|     private readonly CommandTester $electionCommand;
 | |
| 
 | |
|     protected function setUp(): void
 | |
|     {
 | |
|         CondorcetApplication::create();
 | |
| 
 | |
|         $this->electionCommand = new CommandTester(CondorcetApplication::$SymfonyConsoleApplication->find('election'));
 | |
|     }
 | |
| 
 | |
|     public function testConsoleSimpleElection(): void
 | |
|     {
 | |
|         $this->electionCommand->execute(
 | |
|             [
 | |
|                 '--candidates' => 'A;B;C',
 | |
|                 '--votes' => 'A>B>C;C>B>A;B>A>C',
 | |
|                 '--stats' => null,
 | |
|                 '--natural-condorcet' => null,
 | |
|                 '--allows-votes-weight' => null,
 | |
|                 '--no-tie' => null,
 | |
|                 '--list-votes' => null,
 | |
|                 '--deactivate-implicit-ranking' => null,
 | |
|                 '--show-pairwise' => null,
 | |
|             ],
 | |
|             [
 | |
|                 'verbosity' => OutputInterface::VERBOSITY_VERBOSE,
 | |
|             ]
 | |
|         );
 | |
| 
 | |
|         $output = $this->electionCommand->getDisplay();
 | |
| 
 | |
|         self::assertStringContainsString('3 candidates registered || 3 votes registered', $output);
 | |
| 
 | |
|         self::assertStringContainsString('Schulze', $output);
 | |
|         self::assertStringContainsString('Registered candidates', $output);
 | |
|         self::assertStringContainsString('Stats - votes registration', $output);
 | |
|         self::assertStringContainsString('Registered Votes List', $output);
 | |
|         self::assertStringContainsString('Pairwise', $output);
 | |
|         self::assertStringContainsString('Stats:', $output);
 | |
| 
 | |
|         self::assertMatchesRegularExpression('/Is vote weight allowed\?( )+TRUE/', $output);
 | |
|         self::assertMatchesRegularExpression('/Votes are evaluated according to the implicit ranking rule\?( )+FALSE./', $output);
 | |
|         self::assertMatchesRegularExpression('/Is vote tie in rank allowed\?( )+FALSE/', $output);
 | |
|     }
 | |
| 
 | |
|     public function testConsoleSeats(): void
 | |
|     {
 | |
|         $this->electionCommand->execute(
 | |
|             [
 | |
|                 '--candidates' => 'A;B;C',
 | |
|                 '--votes' => 'A>B>C;C>B>A;B>A>C',
 | |
|                 '--stats' => null,
 | |
|                 '--natural-condorcet' => null,
 | |
|                 '--allows-votes-weight' => null,
 | |
|                 '--no-tie' => null,
 | |
|                 '--list-votes' => null,
 | |
|                 '--deactivate-implicit-ranking' => null,
 | |
|                 '--show-pairwise' => null,
 | |
| 
 | |
|                 '--seats' => 42,
 | |
|                 'methods' => ['STV'],
 | |
|             ],
 | |
|             [
 | |
|                 'verbosity' => OutputInterface::VERBOSITY_VERBOSE,
 | |
|             ]
 | |
|         );
 | |
| 
 | |
|         $output = $this->electionCommand->getDisplay();
 | |
| 
 | |
|         self::assertStringContainsString('3 candidates registered || 3 votes registered', $output);
 | |
| 
 | |
|         self::assertStringContainsString('Seats:', $output);
 | |
|         self::assertStringContainsString('42', $output);
 | |
|     }
 | |
| 
 | |
|     public function testQuotas(): void
 | |
|     {
 | |
|         $this->electionCommand->execute([
 | |
|             '--candidates' => 'A;B;C',
 | |
|             '--votes' => 'A>B>C;C>B>A;B>A>C',
 | |
| 
 | |
|             'methods' => ['STV'],
 | |
|         ]);
 | |
| 
 | |
|         $output = $this->electionCommand->getDisplay();
 | |
| 
 | |
|         self::assertMatchesRegularExpression('/Is vote tie in rank allowed\?( )+TRUE/', $output);
 | |
|         self::assertStringContainsString('Droop Quota', $output);
 | |
| 
 | |
|         $this->electionCommand->execute([
 | |
|             '--candidates' => 'A;B;C',
 | |
|             '--votes' => 'A>B>C;C>B>A;B>A>C',
 | |
| 
 | |
|             'methods' => ['STV'],
 | |
|             '--quota' => 'imperiali',
 | |
|         ]);
 | |
| 
 | |
|         $output = $this->electionCommand->getDisplay();
 | |
| 
 | |
|         self::assertStringContainsString('Imperiali', $output);
 | |
|     }
 | |
| 
 | |
|     public function testConsoleAllMethodsArgument(): void
 | |
|     {
 | |
|         $this->electionCommand->execute([
 | |
|             '--candidates' => 'A;B;C',
 | |
|             '--votes' => 'A>B>C;C>B>A;B>A>C',
 | |
| 
 | |
|             'methods' => ['all'],
 | |
|         ]);
 | |
| 
 | |
|         $output = $this->electionCommand->getDisplay();
 | |
|         // \var_dump($output);
 | |
| 
 | |
|         self::assertStringContainsString('Copeland', $output);
 | |
|     }
 | |
| 
 | |
|     public function testConsoleMultiplesMethods(): void
 | |
|     {
 | |
|         $this->electionCommand->execute([
 | |
|             '--candidates' => 'A;B;C',
 | |
|             '--votes' => 'A>B>C;C>B>A;B>A>C',
 | |
| 
 | |
|             'methods' => ['Copeland', 'RankedPairs', 'Minimax'],
 | |
|         ]);
 | |
| 
 | |
|         $output = $this->electionCommand->getDisplay();
 | |
|         // \var_dump($output);
 | |
| 
 | |
|         self::assertStringContainsString('Copeland', $output);
 | |
|         self::assertStringContainsString('Ranked Pairs M', $output);
 | |
|         self::assertStringContainsString('Minimax Winning', $output);
 | |
|     }
 | |
| 
 | |
|     public function testConsoleFileInput(): void
 | |
|     {
 | |
|         $this->electionCommand->execute([
 | |
|             '--candidates' => __DIR__.'/data.candidates',
 | |
|             '--votes' => __DIR__.'/data.votes',
 | |
|         ]);
 | |
| 
 | |
|         $output = $this->electionCommand->getDisplay();
 | |
|         // \var_dump($output);
 | |
| 
 | |
|         self::assertStringContainsString('Schulze', $output);
 | |
|         self::assertStringContainsString('A,B', $output);
 | |
|         self::assertStringContainsString('C '.CondorcetStyle::CONDORCET_LOSER_SYMBOL, $output);
 | |
|     }
 | |
| 
 | |
|     public function testInteractiveCommand(): void
 | |
|     {
 | |
|         $this->electionCommand->setInputs([
 | |
|             'A',
 | |
|             'B',
 | |
|             'C',
 | |
|             '',
 | |
|             'A>B>C',
 | |
|             'B>A>C',
 | |
|             'A>C>B',
 | |
|             '',
 | |
|         ]);
 | |
| 
 | |
|         $this->electionCommand->execute([
 | |
|             'command' => 'election',
 | |
|         ]);
 | |
| 
 | |
|         $output = $this->electionCommand->getDisplay();
 | |
|         // \var_dump($output);
 | |
| 
 | |
|         self::assertStringContainsString('Results: Schulze Winning', $output);
 | |
|     }
 | |
| 
 | |
|     public function testNonInteractionMode(): never
 | |
|     {
 | |
|         $this->expectException(ResultRequestedWithoutVotesException::class);
 | |
|         $this->expectExceptionMessage('The result cannot be requested without votes');
 | |
| 
 | |
|         $this->electionCommand->execute([], ['interactive' => false]);
 | |
| 
 | |
|         // $output = $this->electionCommand->getDisplay();
 | |
|         // \var_dump($output);
 | |
|     }
 | |
| 
 | |
|     public function testCustomizeVotesPerMb(): void
 | |
|     {
 | |
|         $this->electionCommand->execute([
 | |
|             '--candidates' => 'A;B;C',
 | |
|             '--votes' => 'A>B>C;C>B>A;B>A>C',
 | |
|             '--votes-per-mb' => 42,
 | |
|         ]);
 | |
| 
 | |
|         self::assertSame(42, \CondorcetPHP\Condorcet\Console\Commands\ElectionCommand::$VotesPerMB);
 | |
| 
 | |
|         // $output = $this->electionCommand->getDisplay();
 | |
|         // \var_dump($output);
 | |
|     }
 | |
| 
 | |
|     public function testVoteWithDb1(): void
 | |
|     {
 | |
|         ElectionCommand::$forceIniMemoryLimitTo = '128M';
 | |
| 
 | |
|         $this->electionCommand->execute([
 | |
|             '--candidates' => 'A;B;C',
 | |
|             '--votes-per-mb' => 1,
 | |
|             '--votes' => 'A>B>C * '.(((int) preg_replace('`[^0-9]`', '', ElectionCommand::$forceIniMemoryLimitTo)) + 1), # Must be superior to memory limit in MB
 | |
|         ], [
 | |
|             'verbosity' => OutputInterface::VERBOSITY_DEBUG,
 | |
|         ]);
 | |
| 
 | |
|         $output = $this->electionCommand->getDisplay();
 | |
| 
 | |
|         self::assertMatchesRegularExpression('/Votes per Mb +1/', $output);
 | |
|         self::assertMatchesRegularExpression('/Db is used +yes, using path\\:/', $output);
 | |
| 
 | |
|         ElectionCommand::$forceIniMemoryLimitTo = null;
 | |
| 
 | |
|         # And absence of this error: unlink(path): Resource temporarily unavailable
 | |
|     }
 | |
| 
 | |
| 
 | |
|     public function testNaturalCondorcet(): void
 | |
|     {
 | |
|         $this->electionCommand->execute([
 | |
|             '--candidates' => 'A;B;C',
 | |
|             '--votes' => 'A=B=C',
 | |
|             '--natural-condorcet' => true,
 | |
|         ]);
 | |
| 
 | |
|         $output = $this->electionCommand->getDisplay();
 | |
| 
 | |
|         self::assertStringContainsString(CondorcetStyle::CONDORCET_WINNER_SYMBOL.'  Condorcet Winner | -', $output);
 | |
|         self::assertStringContainsString(CondorcetStyle::CONDORCET_LOSER_SYMBOL.'  Condorcet Loser  | -', $output);
 | |
|     }
 | |
| 
 | |
|     public function testFromCondorcetElectionFormat_DoubleCandidates(): void
 | |
|     {
 | |
|         $this->expectException(CandidateExistsException::class);
 | |
| 
 | |
|         $this->electionCommand->execute(
 | |
|             [
 | |
|                 '--candidates' => 'A;B;C',
 | |
|                 '--import-condorcet-election-format' => __DIR__.'/../../Tools/Converters/CondorcetElectionFormatData/test1.cvotes',
 | |
|             ],
 | |
|             [
 | |
|                 'verbosity' => OutputInterface::VERBOSITY_VERBOSE,
 | |
|             ]
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     public function testFromCondorcetElectionFormat_ArgumentpriorityAndDoubleVoteArgument(): void
 | |
|     {
 | |
|         $this->electionCommand->execute(
 | |
|             [
 | |
|                 '--import-condorcet-election-format' => __DIR__.'/../../Tools/Converters/CondorcetElectionFormatData/test1.cvotes',
 | |
|                 '--votes' => 'C>A',
 | |
|                 '--deactivate-implicit-ranking' => null,
 | |
|                 '--no-tie' => null,
 | |
|                 '--allows-votes-weight' => null,
 | |
|             ],
 | |
|             [
 | |
|                 'verbosity' => OutputInterface::VERBOSITY_VERBOSE,
 | |
|             ]
 | |
|         );
 | |
| 
 | |
|         $output = $this->electionCommand->getDisplay();
 | |
| 
 | |
|         self::assertStringContainsString('3 candidates registered || 2 votes registered', $output);
 | |
| 
 | |
|         self::assertStringContainsString('Schulze', $output);
 | |
|         self::assertStringContainsString('Registered candidates', $output);
 | |
|         self::assertStringContainsString('Stats - votes registration', $output);
 | |
| 
 | |
|         self::assertMatchesRegularExpression('/Is vote weight allowed\?( )+TRUE/', $output);
 | |
|         self::assertMatchesRegularExpression('/Votes are evaluated according to the implicit ranking rule\?( )+FALSE./', $output);
 | |
|         self::assertMatchesRegularExpression('/Is vote tie in rank allowed\?( )+FALSE/', $output);
 | |
| 
 | |
|         self::assertStringContainsString('Sum vote weight | 3', $output);
 | |
|     }
 | |
| 
 | |
|     public function testFromCondorcetElectionFormat_Arguments(): void
 | |
|     {
 | |
|         $this->electionCommand->execute(
 | |
|             [
 | |
|                 '--import-condorcet-election-format' => __DIR__.'/../../Tools/Converters/CondorcetElectionFormatData/test2.cvotes',
 | |
|             ],
 | |
|             [
 | |
|                 'verbosity' => OutputInterface::VERBOSITY_VERBOSE,
 | |
|             ]
 | |
|         );
 | |
| 
 | |
|         $output = $this->electionCommand->getDisplay();
 | |
| 
 | |
|         self::assertStringContainsString('3 candidates registered || 2 votes registered', $output);
 | |
| 
 | |
|         self::assertStringContainsString('Schulze', $output);
 | |
|         self::assertStringContainsString('Registered candidates', $output);
 | |
|         self::assertStringContainsString('Stats - votes registration', $output);
 | |
| 
 | |
|         self::assertMatchesRegularExpression('/Is vote weight allowed\?( )+FALSE/', $output);
 | |
|         self::assertMatchesRegularExpression('/Votes are evaluated according to the implicit ranking rule\?( )+FALSE./', $output);
 | |
|         self::assertMatchesRegularExpression('/Is vote tie in rank allowed\?( )+TRUE/', $output);
 | |
| 
 | |
|         self::assertStringContainsString('Sum vote weight | 2', $output);
 | |
| 
 | |
|         self::assertStringContainsString('B '.CondorcetStyle::CONDORCET_WINNER_SYMBOL, $output); # Condorcet Winner
 | |
|     }
 | |
| 
 | |
|     public function testVoteWithDb_CondorcetElectionFormat(): void
 | |
|     {
 | |
|         ElectionCommand::$forceIniMemoryLimitTo = '128M';
 | |
| 
 | |
|         $this->electionCommand->execute([
 | |
|             '--votes-per-mb' => 1,
 | |
|             '--import-condorcet-election-format' => __DIR__.'/../../Tools/Converters/CondorcetElectionFormatData/test3.cvotes',
 | |
|         ], [
 | |
|             'verbosity' => OutputInterface::VERBOSITY_DEBUG,
 | |
|         ]);
 | |
| 
 | |
|         $output = $this->electionCommand->getDisplay();
 | |
| 
 | |
|         self::assertMatchesRegularExpression('/Votes per Mb +1/', $output);
 | |
|         self::assertStringContainsString('Db is used', $output);
 | |
|         self::assertStringContainsString('yes, using path:', $output);
 | |
| 
 | |
|         ElectionCommand::$forceIniMemoryLimitTo = null;
 | |
| 
 | |
|         # And absence of this error: unlink(path): Resource temporarily unavailable
 | |
|     }
 | |
| 
 | |
|     public function testFromDebianFormat(): void
 | |
|     {
 | |
|         $this->electionCommand->execute(
 | |
|             [
 | |
|                 '--import-debian-format' => __DIR__.'/../../Tools/Converters/DebianData/leader2020_tally.txt',
 | |
|                 'methods' => ['STV'],
 | |
|             ],
 | |
|             [
 | |
|                 'verbosity' => OutputInterface::VERBOSITY_VERBOSE,
 | |
|             ]
 | |
|         );
 | |
| 
 | |
|         $output = $this->electionCommand->getDisplay();
 | |
| 
 | |
|         self::assertStringContainsString('4 candidates registered || 339 votes registered', $output);
 | |
| 
 | |
|         self::assertStringContainsString('STV', $output);
 | |
|         self::assertStringContainsString('Registered candidates', $output);
 | |
|         self::assertStringContainsString('Stats - votes registration', $output);
 | |
| 
 | |
|         self::assertMatchesRegularExpression('/Is vote weight allowed\?( )+FALSE/', $output);
 | |
|         self::assertMatchesRegularExpression('/Votes are evaluated according to the implicit ranking rule\?( )+TRUE./', $output);
 | |
|         self::assertMatchesRegularExpression('/Is vote tie in rank allowed\?( )+TRUE/', $output);
 | |
| 
 | |
|         self::assertStringContainsString('Sum vote weight | 339', $output);
 | |
| 
 | |
|         self::assertStringContainsString('Jonathan Carter '.CondorcetStyle::CONDORCET_WINNER_SYMBOL, $output); # Condorcet Winner
 | |
|         self::assertMatchesRegularExpression('/Seats: *\| 1/', $output);
 | |
|     }
 | |
| 
 | |
|     public function testFromDavidHillFormat(): void
 | |
|     {
 | |
|         $this->electionCommand->execute(
 | |
|             [
 | |
|                 '--import-david-hill-format' => __DIR__.'/../../Tools/Converters/TidemanData/A1.HIL',
 | |
|                 'methods' => ['STV'],
 | |
|             ],
 | |
|             [
 | |
|                 'verbosity' => OutputInterface::VERBOSITY_VERBOSE,
 | |
|             ]
 | |
|         );
 | |
| 
 | |
|         $output = $this->electionCommand->getDisplay();
 | |
| 
 | |
|         self::assertStringContainsString('10 candidates registered || 380 votes registered', $output);
 | |
| 
 | |
|         self::assertStringContainsString('STV', $output);
 | |
|         self::assertStringContainsString('Registered candidates', $output);
 | |
|         self::assertStringContainsString('Stats - votes registration', $output);
 | |
| 
 | |
|         self::assertMatchesRegularExpression('/Is vote weight allowed\?( )+FALSE/', $output);
 | |
|         self::assertMatchesRegularExpression('/Votes are evaluated according to the implicit ranking rule\?( )+TRUE./', $output);
 | |
|         self::assertMatchesRegularExpression('/Is vote tie in rank allowed\?( )+TRUE/', $output);
 | |
| 
 | |
|         self::assertStringContainsString('Sum vote weight | 380', $output);
 | |
| 
 | |
|         self::assertStringContainsString('Candidate  1 '.CondorcetStyle::CONDORCET_WINNER_SYMBOL, $output); # Condorcet Winner
 | |
|         self::assertMatchesRegularExpression('/Seats: *\| 3/', $output);
 | |
|     }
 | |
| }
 |