2021-01-12 23:54:56 +11:00
|
|
|
# pyRCV2: Preferential vote counting
|
|
|
|
# Copyright © 2020–2021 Lee Yingtong Li (RunasSudo)
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Affero General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU Affero General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
from pyRCV2.model import Candidate, CountCard
|
|
|
|
from pyRCV2.numbers import Num
|
|
|
|
from pyRCV2.ties import TiesBackwards, TiesForwards, TiesPrompt, TiesRandom
|
|
|
|
import tests.util
|
|
|
|
|
|
|
|
def test_prompt_py(monkeypatch):
|
|
|
|
l = [
|
|
|
|
(Candidate('A'), CountCard()),
|
|
|
|
(Candidate('B'), CountCard()),
|
|
|
|
(Candidate('C'), CountCard()),
|
|
|
|
]
|
|
|
|
t = TiesPrompt()
|
2021-01-14 00:16:36 +11:00
|
|
|
monkeypatch.setattr('builtins.input', lambda _: '1')
|
|
|
|
assert t.choose_lowest(l) == l[0]
|
|
|
|
assert t.choose_highest(l) == l[0]
|
|
|
|
monkeypatch.setattr('builtins.input', lambda _: '2')
|
2021-01-12 23:54:56 +11:00
|
|
|
assert t.choose_lowest(l) == l[1]
|
|
|
|
assert t.choose_highest(l) == l[1]
|
|
|
|
|
|
|
|
def test_prompt_js():
|
|
|
|
ctx = tests.util.init_context()
|
|
|
|
ctx.eval('let l = [[py.pyRCV2.model.Candidate("A"), py.pyRCV2.model.CountCard()], [py.pyRCV2.model.Candidate("B"), py.pyRCV2.model.CountCard()], [py.pyRCV2.model.Candidate("C"), py.pyRCV2.model.CountCard()]];')
|
|
|
|
ctx.eval('let tie = py.pyRCV2.ties.TiesPrompt();')
|
|
|
|
ctx.eval('let raised = false; try { tie.choose_lowest(l); } catch (ex) { if (py.isinstance(ex, py.pyRCV2.ties.RequireInput)) { raised = true; } }')
|
|
|
|
assert ctx.eval('raised') == True
|
|
|
|
|
2021-01-14 00:16:36 +11:00
|
|
|
ctx.eval('tie.buffer = "1";')
|
|
|
|
assert ctx.eval('tie.choose_lowest(l) === l[0]')
|
|
|
|
ctx.eval('tie.buffer = "1";')
|
|
|
|
assert ctx.eval('tie.choose_highest(l) === l[0]')
|
2021-01-12 23:54:56 +11:00
|
|
|
ctx.eval('tie.buffer = "2";')
|
|
|
|
assert ctx.eval('tie.choose_lowest(l) === l[1]')
|
|
|
|
ctx.eval('tie.buffer = "2";')
|
|
|
|
assert ctx.eval('tie.choose_highest(l) === l[1]')
|
|
|
|
|
|
|
|
class Stub:
|
|
|
|
pass
|
|
|
|
|
|
|
|
def test_backwards_py():
|
|
|
|
l = [
|
|
|
|
(Candidate('A'), CountCard()),
|
|
|
|
(Candidate('B'), CountCard()),
|
|
|
|
(Candidate('C'), CountCard()),
|
|
|
|
]
|
|
|
|
|
|
|
|
# Prepare previous results
|
|
|
|
result1 = Stub()
|
|
|
|
result1.candidates = {l[0][0]: CountCard(), l[1][0]: CountCard(), l[2][0]: CountCard()}
|
|
|
|
result1.candidates[l[0][0]].orig_votes = Num(10)
|
|
|
|
result1.candidates[l[1][0]].orig_votes = Num(20)
|
|
|
|
result1.candidates[l[2][0]].orig_votes = Num(30)
|
|
|
|
|
|
|
|
result2 = Stub()
|
|
|
|
result2.candidates = {l[0][0]: CountCard(), l[1][0]: CountCard(), l[2][0]: CountCard()}
|
|
|
|
result2.candidates[l[0][0]].orig_votes = Num(30)
|
|
|
|
result2.candidates[l[1][0]].orig_votes = Num(20)
|
|
|
|
result2.candidates[l[2][0]].orig_votes = Num(10)
|
|
|
|
|
|
|
|
counter = Stub()
|
|
|
|
counter.step_results = [result1, result2]
|
|
|
|
|
|
|
|
t = TiesBackwards(counter)
|
|
|
|
assert t.choose_lowest(l) == l[2]
|
|
|
|
assert t.choose_highest(l) == l[0]
|
|
|
|
|
|
|
|
def test_backwards_js():
|
|
|
|
ctx = tests.util.init_context()
|
|
|
|
ctx.eval('let l = [[py.pyRCV2.model.Candidate("A"), py.pyRCV2.model.CountCard()], [py.pyRCV2.model.Candidate("B"), py.pyRCV2.model.CountCard()], [py.pyRCV2.model.Candidate("C"), py.pyRCV2.model.CountCard()]];')
|
|
|
|
|
|
|
|
# Prepare previous results
|
|
|
|
ctx.eval('let result1 = {candidates: py.pyRCV2.safedict.SafeDict([[l[0][0], {votes: py.pyRCV2.numbers.Num(10)}], [l[1][0], {votes: py.pyRCV2.numbers.Num(20)}], [l[2][0], {votes: py.pyRCV2.numbers.Num(30)}]])};')
|
|
|
|
ctx.eval('let result2 = {candidates: py.pyRCV2.safedict.SafeDict([[l[0][0], {votes: py.pyRCV2.numbers.Num(30)}], [l[1][0], {votes: py.pyRCV2.numbers.Num(20)}], [l[2][0], {votes: py.pyRCV2.numbers.Num(10)}]])};')
|
|
|
|
ctx.eval('let counter = {step_results: [result1, result2]};')
|
|
|
|
|
|
|
|
ctx.eval('let tie = py.pyRCV2.ties.TiesBackwards(counter);')
|
|
|
|
assert ctx.eval('tie.choose_lowest(l) === l[2]')
|
|
|
|
assert ctx.eval('tie.choose_highest(l) === l[0]')
|
|
|
|
|
|
|
|
def test_forwards_py():
|
|
|
|
l = [
|
|
|
|
(Candidate('A'), CountCard()),
|
|
|
|
(Candidate('B'), CountCard()),
|
|
|
|
(Candidate('C'), CountCard()),
|
|
|
|
]
|
|
|
|
|
|
|
|
# Prepare previous results
|
|
|
|
result1 = Stub()
|
|
|
|
result1.candidates = {l[0][0]: CountCard(), l[1][0]: CountCard(), l[2][0]: CountCard()}
|
|
|
|
result1.candidates[l[0][0]].orig_votes = Num(10)
|
|
|
|
result1.candidates[l[1][0]].orig_votes = Num(20)
|
|
|
|
result1.candidates[l[2][0]].orig_votes = Num(30)
|
|
|
|
|
|
|
|
result2 = Stub()
|
|
|
|
result2.candidates = {l[0][0]: CountCard(), l[1][0]: CountCard(), l[2][0]: CountCard()}
|
|
|
|
result2.candidates[l[0][0]].orig_votes = Num(30)
|
|
|
|
result2.candidates[l[1][0]].orig_votes = Num(20)
|
|
|
|
result2.candidates[l[2][0]].orig_votes = Num(10)
|
|
|
|
|
|
|
|
counter = Stub()
|
|
|
|
counter.step_results = [result1, result2]
|
|
|
|
|
|
|
|
t = TiesForwards(counter)
|
|
|
|
assert t.choose_lowest(l) == l[0]
|
|
|
|
assert t.choose_highest(l) == l[2]
|
|
|
|
|
|
|
|
def test_forwards_js():
|
|
|
|
ctx = tests.util.init_context()
|
|
|
|
ctx.eval('let l = [[py.pyRCV2.model.Candidate("A"), py.pyRCV2.model.CountCard()], [py.pyRCV2.model.Candidate("B"), py.pyRCV2.model.CountCard()], [py.pyRCV2.model.Candidate("C"), py.pyRCV2.model.CountCard()]];')
|
|
|
|
|
|
|
|
# Prepare previous results
|
|
|
|
ctx.eval('let result1 = {candidates: py.pyRCV2.safedict.SafeDict([[l[0][0], {votes: py.pyRCV2.numbers.Num(10)}], [l[1][0], {votes: py.pyRCV2.numbers.Num(20)}], [l[2][0], {votes: py.pyRCV2.numbers.Num(30)}]])};')
|
|
|
|
ctx.eval('let result2 = {candidates: py.pyRCV2.safedict.SafeDict([[l[0][0], {votes: py.pyRCV2.numbers.Num(30)}], [l[1][0], {votes: py.pyRCV2.numbers.Num(20)}], [l[2][0], {votes: py.pyRCV2.numbers.Num(10)}]])};')
|
|
|
|
ctx.eval('let counter = {step_results: [result1, result2]};')
|
|
|
|
|
|
|
|
ctx.eval('let tie = py.pyRCV2.ties.TiesForwards(counter);')
|
|
|
|
assert ctx.eval('tie.choose_lowest(l) === l[0]')
|
|
|
|
assert ctx.eval('tie.choose_highest(l) === l[2]')
|
|
|
|
|
|
|
|
def test_random_py():
|
|
|
|
l = [
|
|
|
|
(Candidate('A'), CountCard()),
|
|
|
|
(Candidate('B'), CountCard()),
|
|
|
|
(Candidate('C'), CountCard()),
|
|
|
|
]
|
|
|
|
t = TiesRandom('foobar') # first number is 0, second number is 0
|
|
|
|
assert t.choose_lowest(l) == l[0]
|
|
|
|
assert t.choose_highest(l) == l[0]
|
|
|
|
|
|
|
|
def test_random_js():
|
|
|
|
ctx = tests.util.init_context()
|
|
|
|
ctx.eval('let l = [[py.pyRCV2.model.Candidate("A"), py.pyRCV2.model.CountCard()], [py.pyRCV2.model.Candidate("B"), py.pyRCV2.model.CountCard()], [py.pyRCV2.model.Candidate("C"), py.pyRCV2.model.CountCard()]];')
|
|
|
|
ctx.eval('let tie = py.pyRCV2.ties.TiesRandom("foobar")')
|
|
|
|
assert ctx.eval('tie.choose_lowest(l) === l[0]')
|
|
|
|
assert ctx.eval('tie.choose_highest(l) === l[0]')
|