Add tests for tie breakers
This commit is contained in:
parent
5812b252d8
commit
ad7efe3133
@ -76,7 +76,6 @@ class TiesPrompt:
|
|||||||
def choose_highest(self, l):
|
def choose_highest(self, l):
|
||||||
return self.choose_lowest(l)
|
return self.choose_lowest(l)
|
||||||
|
|
||||||
# FIXME: This is untested!
|
|
||||||
class TiesBackwards:
|
class TiesBackwards:
|
||||||
"""
|
"""
|
||||||
Break ties based on the candidate who had the highest/lowest total at the end
|
Break ties based on the candidate who had the highest/lowest total at the end
|
||||||
@ -107,7 +106,6 @@ class TiesBackwards:
|
|||||||
__pragma__('noopov')
|
__pragma__('noopov')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# FIXME: This is untested!
|
|
||||||
class TiesForwards:
|
class TiesForwards:
|
||||||
"""
|
"""
|
||||||
Break ties based on the candidate who had the highest/lowest total at the end
|
Break ties based on the candidate who had the highest/lowest total at the end
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# pyRCV2: Preferential vote counting
|
# pyRCV2: Preferential vote counting
|
||||||
# Copyright © 2020 Lee Yingtong Li (RunasSudo)
|
# Copyright © 2020–2021 Lee Yingtong Li (RunasSudo)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -19,6 +19,7 @@ import pyRCV2.model
|
|||||||
import pyRCV2.method, pyRCV2.method.base_stv, pyRCV2.method.gregory, pyRCV2.method.meek
|
import pyRCV2.method, pyRCV2.method.base_stv, pyRCV2.method.gregory, pyRCV2.method.meek
|
||||||
import pyRCV2.numbers
|
import pyRCV2.numbers
|
||||||
import pyRCV2.random
|
import pyRCV2.random
|
||||||
|
import pyRCV2.safedict
|
||||||
import pyRCV2.ties
|
import pyRCV2.ties
|
||||||
|
|
||||||
__pragma__('js', '{}', 'export {pyRCV2, isinstance, repr, str};')
|
__pragma__('js', '{}', 'export {pyRCV2, isinstance, repr, str};')
|
||||||
|
@ -18,9 +18,9 @@ import pyRCV2.blt
|
|||||||
import pyRCV2.numbers
|
import pyRCV2.numbers
|
||||||
import pyRCV2.method.gregory, pyRCV2.method.meek
|
import pyRCV2.method.gregory, pyRCV2.method.meek
|
||||||
from pyRCV2.model import CountCompleted
|
from pyRCV2.model import CountCompleted
|
||||||
|
import tests.util
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from py_mini_racer import py_mini_racer
|
|
||||||
|
|
||||||
def maketst(numbers, counter_cls, options, options_description):
|
def maketst(numbers, counter_cls, options, options_description):
|
||||||
def t_py():
|
def t_py():
|
||||||
@ -41,17 +41,7 @@ def maketst(numbers, counter_cls, options, options_description):
|
|||||||
# TODO: Do some sanity checks
|
# TODO: Do some sanity checks
|
||||||
|
|
||||||
def t_js():
|
def t_js():
|
||||||
ctx = py_mini_racer.MiniRacer()
|
ctx = tests.util.init_context()
|
||||||
|
|
||||||
# Imports
|
|
||||||
with open('html/vendor/BigInt_BigRat-a5f89e2.min.js', 'r') as f:
|
|
||||||
ctx.eval(f.read())
|
|
||||||
with open('html/vendor/big-6.0.0.min.js', 'r') as f:
|
|
||||||
ctx.eval(f.read())
|
|
||||||
with open('html/vendor/sjcl-1.0.8.min.js', 'r') as f:
|
|
||||||
ctx.eval(f.read())
|
|
||||||
with open('html/bundle.js', 'r') as f:
|
|
||||||
ctx.eval(f.read())
|
|
||||||
|
|
||||||
ctx.eval('py.pyRCV2.numbers.set_numclass(py.pyRCV2.numbers.Fixed);')
|
ctx.eval('py.pyRCV2.numbers.set_numclass(py.pyRCV2.numbers.Fixed);')
|
||||||
ctx.eval('py.pyRCV2.numbers.set_dps(5);')
|
ctx.eval('py.pyRCV2.numbers.set_dps(5);')
|
||||||
|
@ -16,8 +16,7 @@
|
|||||||
|
|
||||||
import pyRCV2.numbers
|
import pyRCV2.numbers
|
||||||
from pyRCV2.numbers import Num
|
from pyRCV2.numbers import Num
|
||||||
|
import tests.util
|
||||||
from py_mini_racer import py_mini_racer
|
|
||||||
|
|
||||||
def maketst(numbers, dps, method, result):
|
def maketst(numbers, dps, method, result):
|
||||||
def t_py():
|
def t_py():
|
||||||
@ -30,23 +29,13 @@ def maketst(numbers, dps, method, result):
|
|||||||
assert getattr(num1, method)(num2) == Num(result)
|
assert getattr(num1, method)(num2) == Num(result)
|
||||||
|
|
||||||
def t_js():
|
def t_js():
|
||||||
ctx = py_mini_racer.MiniRacer()
|
ctx = tests.util.init_context()
|
||||||
|
|
||||||
# Imports
|
|
||||||
with open('html/vendor/BigInt_BigRat-a5f89e2.min.js', 'r') as f:
|
|
||||||
ctx.eval(f.read())
|
|
||||||
with open('html/vendor/big-6.0.0.min.js', 'r') as f:
|
|
||||||
ctx.eval(f.read())
|
|
||||||
with open('html/vendor/sjcl-1.0.8.min.js', 'r') as f:
|
|
||||||
ctx.eval(f.read())
|
|
||||||
with open('html/bundle.js', 'r') as f:
|
|
||||||
ctx.eval(f.read())
|
|
||||||
|
|
||||||
ctx.eval('py.pyRCV2.numbers.set_numclass(py.pyRCV2.numbers.{});'.format(numbers))
|
ctx.eval('py.pyRCV2.numbers.set_numclass(py.pyRCV2.numbers.{});'.format(numbers))
|
||||||
ctx.eval('py.pyRCV2.numbers.set_dps({});'.format(dps))
|
ctx.eval('py.pyRCV2.numbers.set_dps({});'.format(dps))
|
||||||
|
|
||||||
ctx.eval('num1 = py.pyRCV2.numbers.Num("314.15"); void(0);')
|
ctx.eval('let num1 = py.pyRCV2.numbers.Num("314.15");')
|
||||||
ctx.eval('num2 = py.pyRCV2.numbers.Num("42.42"); void(0);')
|
ctx.eval('let num2 = py.pyRCV2.numbers.Num("42.42");')
|
||||||
|
|
||||||
assert ctx.eval('num1.{}(num2).__eq__(py.pyRCV2.numbers.Num("{}"))'.format(method, result))
|
assert ctx.eval('num1.{}(num2).__eq__(py.pyRCV2.numbers.Num("{}"))'.format(method, result))
|
||||||
|
|
||||||
@ -66,22 +55,12 @@ def maketst_round(numbers, dps, num, dps_round, mode_round, result):
|
|||||||
assert num1.round(dps_round, getattr(num1, mode_round)) == Num(result)
|
assert num1.round(dps_round, getattr(num1, mode_round)) == Num(result)
|
||||||
|
|
||||||
def t_js():
|
def t_js():
|
||||||
ctx = py_mini_racer.MiniRacer()
|
ctx = tests.util.init_context()
|
||||||
|
|
||||||
# Imports
|
|
||||||
with open('html/vendor/BigInt_BigRat-a5f89e2.min.js', 'r') as f:
|
|
||||||
ctx.eval(f.read())
|
|
||||||
with open('html/vendor/big-6.0.0.min.js', 'r') as f:
|
|
||||||
ctx.eval(f.read())
|
|
||||||
with open('html/vendor/sjcl-1.0.8.min.js', 'r') as f:
|
|
||||||
ctx.eval(f.read())
|
|
||||||
with open('html/bundle.js', 'r') as f:
|
|
||||||
ctx.eval(f.read())
|
|
||||||
|
|
||||||
ctx.eval('py.pyRCV2.numbers.set_numclass(py.pyRCV2.numbers.{});'.format(numbers))
|
ctx.eval('py.pyRCV2.numbers.set_numclass(py.pyRCV2.numbers.{});'.format(numbers))
|
||||||
ctx.eval('py.pyRCV2.numbers.set_dps({});'.format(dps))
|
ctx.eval('py.pyRCV2.numbers.set_dps({});'.format(dps))
|
||||||
|
|
||||||
ctx.eval('num1 = py.pyRCV2.numbers.Num("{}"); void(0);'.format(num))
|
ctx.eval('let num1 = py.pyRCV2.numbers.Num("{}");'.format(num))
|
||||||
assert ctx.eval('num1.round({}, num1.{}).__eq__(py.pyRCV2.numbers.Num("{}"))'.format(dps_round, mode_round, result))
|
assert ctx.eval('num1.round({}, num1.{}).__eq__(py.pyRCV2.numbers.Num("{}"))'.format(dps_round, mode_round, result))
|
||||||
|
|
||||||
return t_py, t_js
|
return t_py, t_js
|
||||||
|
@ -15,8 +15,7 @@
|
|||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from pyRCV2.random import SHARandom
|
from pyRCV2.random import SHARandom
|
||||||
|
import tests.util
|
||||||
from py_mini_racer import py_mini_racer
|
|
||||||
|
|
||||||
def test_sharandom_py():
|
def test_sharandom_py():
|
||||||
random = SHARandom('foobar')
|
random = SHARandom('foobar')
|
||||||
@ -25,17 +24,7 @@ def test_sharandom_py():
|
|||||||
assert random.next(64) == 13
|
assert random.next(64) == 13
|
||||||
|
|
||||||
def test_sharandom_js():
|
def test_sharandom_js():
|
||||||
ctx = py_mini_racer.MiniRacer()
|
ctx = tests.util.init_context()
|
||||||
|
|
||||||
# Imports
|
|
||||||
with open('html/vendor/BigInt_BigRat-a5f89e2.min.js', 'r') as f:
|
|
||||||
ctx.eval(f.read())
|
|
||||||
with open('html/vendor/big-6.0.0.min.js', 'r') as f:
|
|
||||||
ctx.eval(f.read())
|
|
||||||
with open('html/vendor/sjcl-1.0.8.min.js', 'r') as f:
|
|
||||||
ctx.eval(f.read())
|
|
||||||
with open('html/bundle.js', 'r') as f:
|
|
||||||
ctx.eval(f.read())
|
|
||||||
|
|
||||||
ctx.eval('let random = py.pyRCV2.random.SHARandom("foobar");')
|
ctx.eval('let random = py.pyRCV2.random.SHARandom("foobar");')
|
||||||
assert ctx.eval('random.py_next(10)') == 0
|
assert ctx.eval('random.py_next(10)') == 0
|
||||||
|
145
tests/test_ties.py
Normal file
145
tests/test_ties.py
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
# 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):
|
||||||
|
monkeypatch.setattr('builtins.input', lambda _: '2')
|
||||||
|
l = [
|
||||||
|
(Candidate('A'), CountCard()),
|
||||||
|
(Candidate('B'), CountCard()),
|
||||||
|
(Candidate('C'), CountCard()),
|
||||||
|
]
|
||||||
|
t = TiesPrompt()
|
||||||
|
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
|
||||||
|
|
||||||
|
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]')
|
32
tests/util.py
Normal file
32
tests/util.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# 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/>.
|
||||||
|
|
||||||
|
from py_mini_racer import py_mini_racer
|
||||||
|
|
||||||
|
def init_context():
|
||||||
|
ctx = py_mini_racer.MiniRacer()
|
||||||
|
|
||||||
|
# Imports
|
||||||
|
with open('html/vendor/BigInt_BigRat-a5f89e2.min.js', 'r') as f:
|
||||||
|
ctx.eval(f.read())
|
||||||
|
with open('html/vendor/big-6.0.0.min.js', 'r') as f:
|
||||||
|
ctx.eval(f.read())
|
||||||
|
with open('html/vendor/sjcl-1.0.8.min.js', 'r') as f:
|
||||||
|
ctx.eval(f.read())
|
||||||
|
with open('html/bundle.js', 'r') as f:
|
||||||
|
ctx.eval(f.read())
|
||||||
|
|
||||||
|
return ctx
|
Reference in New Issue
Block a user