From 50bcf82833aaae8f99fc9b82d886c7191c117e0c Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 7 May 2018 13:27:48 -0400 Subject: [PATCH] FIX: Caps and pytest (#482) * FIX: Caps and pytest * FIX: Check in code * FIX: Check for unique names * FIX: Minor fixes * FIX: More checks --- .gitignore | 1 + .travis.yml | 4 +- appveyor.yml | 6 +- codespell_lib/_codespell.py | 6 +- codespell_lib/data/dictionary.txt | 56 ++++---- codespell_lib/tests/test_basic.py | 204 ++++++++++++++++++------------ setup.cfg | 7 +- 7 files changed, 161 insertions(+), 123 deletions(-) diff --git a/.gitignore b/.gitignore index 1c06f43d..d7fb8840 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ ld codespell.egg-info *.pyc .cache/ +.pytest_cache/ diff --git a/.travis.yml b/.travis.yml index 712e1c52..86227631 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,7 +39,7 @@ before_install: - source venv/bin/activate - python --version # just to check - pip install -U pip wheel # upgrade to latest pip find 3.5 wheels; wheel to avoid errors - - retry pip install nose flake8 coverage codecov chardet setuptools + - retry pip install pytest pytest-cov pytest-sugar flake8 coverage codecov chardet setuptools - cd $SRC_DIR install: @@ -51,7 +51,7 @@ script: # this file has an error - "! codespell codespell_lib/tests/test_basic.py" - flake8 - - nosetests + - pytest codespell_lib after_success: - codecov diff --git a/appveyor.yml b/appveyor.yml index bcb32591..82246372 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,8 +1,8 @@ environment: global: PYTHON: "C:\\conda" - CONDA_DEPENDENCIES: "nose setuptools flake8 coverage chardet" - PIP_DEPENDENCIES: "codecov" + CONDA_DEPENDENCIES: "pytest pytest-cov setuptools flake8 coverage chardet" + PIP_DEPENDENCIES: "codecov pytest-sugar" matrix: - PYTHON_VERSION: "2.7" PYTHON_ARCH: "32" @@ -21,7 +21,7 @@ build: false # Not a C# project, build stuff at the test step instead. test_script: - "codespell --help" - "flake8" - - "nosetests" + - "pytest codespell_lib" on_success: - "codecov" diff --git a/codespell_lib/_codespell.py b/codespell_lib/_codespell.py index c3437104..d0f5f4d4 100755 --- a/codespell_lib/_codespell.py +++ b/codespell_lib/_codespell.py @@ -304,6 +304,10 @@ def build_dict(filename): with codecs.open(filename, mode='r', buffering=1, encoding='utf-8') as f: for line in f: [key, data] = line.split('->') + # TODO for now, convert both to lower. Someday we can maybe add + # support for fixing caps. + key = key.lower() + data = data.lower() if key in ignore_words: continue data = data.strip() @@ -588,7 +592,7 @@ def main(*args): dictionaries = options.dictionary or [default_dictionary] for dictionary in dictionaries: - if dictionary is "-": + if dictionary == "-": dictionary = default_dictionary if not os.path.exists(dictionary): print('ERROR: cannot find dictionary file: %s' % dictionary, diff --git a/codespell_lib/data/dictionary.txt b/codespell_lib/data/dictionary.txt index 38e13cbc..5684288b 100644 --- a/codespell_lib/data/dictionary.txt +++ b/codespell_lib/data/dictionary.txt @@ -192,7 +192,6 @@ achievment->achievement achievments->achievements achitecture->architecture achitectures->architectures -achive->achieve achive->achieve, archive, achived->achieved, archived, achivement->achievement @@ -784,7 +783,7 @@ aparment->apartment apear->appear apeends->appends apendix->appendix -apenines->apennines, Apennines, +apenines->Apennines aplication->application aplied->applied apllicatin->application @@ -809,7 +808,7 @@ appedn->append appendent->appended appendign->appending appeneded->appended -appenines->apennines, Apennines, +appenines->Apennines appens->appends apperance->appearance apperances->appearances @@ -1533,7 +1532,7 @@ bootstapped->bootstrapped bootstapping->bootstrapping bootstaps->bootstraps boradcast->broadcast -bordrelines->borderline +bordreline->borderline bordrelines->borderlines borke->broke bothe->both @@ -1903,8 +1902,7 @@ chanel->channel changable->changeable changuing->changing chanined->chained -chaning->chaining -chaning->changing +chaning->chaining, changing, channle->channel channles->channels channnel->channel @@ -2114,7 +2112,7 @@ codepoitn->codepoint codespel->codespell coditions->conditions coefficent->coefficient -coefficents->coefficients +coefficents->coefficients coeficent->coefficient coeficents->coefficients cofigure->configure @@ -2326,7 +2324,6 @@ competance->competence competant->competent competative->competitive competion->competition, completion, -competion->completion competions->completions competitiion->competition competive->competitive @@ -2971,7 +2968,6 @@ covnert->convert coyp->copy coypright->copyright cpation->caption -cpoy->copy cpoy->coy, copy, craete->create crahses->crashes @@ -3052,7 +3048,7 @@ cursro->cursor customable->customizable custome->custom, costume, custumized->customized -cutom->custom +cutom->custom cuurently->currently cxan->cyan cycic->cyclic @@ -3125,8 +3121,7 @@ decalratiosn->declarations deccelerate->decelerate decendant->descendant decendants->descendants -decendent->descendant -decendent->descendent +decendent->descendent, descendant, decendents->descendants decideable->decidable decidely->decidedly @@ -4197,7 +4192,7 @@ emegrency->emergency eminate->emanate eminated->emanated emision->emission -emiss->remiss, amiss, amass, +emiss->remiss, amiss, amass, emissed->amassed, amiss, emited->emitted emiting->emitting @@ -4744,7 +4739,7 @@ explitly->explicitly explizit->explicit explizitly->explicitly exploititive->exploitative -explot->exploit, explore, +explot->exploit, explore, explotation->exploitation, exploration, exploting->exploiting, exploring, exponetial->exponential @@ -4934,7 +4929,7 @@ firey->fiery firmwware->firmware firsr->first firt->first, flirt, -firts->first, flirts, +firts->first, flirts, fisical->physical, fiscal, fisionable->fissionable fisisist->physicist @@ -4976,7 +4971,7 @@ foget->forget fogot->forgot fogotten->forgotten folde->folder, fold, -folling->following, falling, +folling->following, falling, folllow->follow folllowed->followed folllowing->following @@ -5704,7 +5699,6 @@ imigrated->emigrated, immigrated, imigration->emigration, immigration, imilar->similar iminent->eminent, imminent, immanent, -iminent->imminent immeadiately->immediately immedate->immediate immedately->immediately @@ -6281,7 +6275,6 @@ intiailises->initialises intiailize->initialize intiailized->initialized intiailizes->initializes -intiailizing->initialising intiailizing->initializing intial->initial intialisation->initialisation @@ -6669,7 +6662,6 @@ mamalian->mammalian mamory->memory managable->manageable, manageably, managment->management -mananged->managed manangement->management mandetory->mandatory maneouvre->manoeuvre @@ -7030,8 +7022,7 @@ moent->moment moeny->money mofdified->modified mohammedans->muslims -moil->mohel -moil->soil +moil->soil, mohel, moint->mount moleclues->molecules momemtn->moment @@ -7057,19 +7048,17 @@ mordern->modern moreso->more, more so, morever->moreover morgage->mortgage -Morisette->Morissette -morover->moreover -Morrisette->Morissette +morisette->morissette +morrisette->morissette morroccan->moroccan morrocco->morocco morroco->morocco mortage->mortgage -mose->more -mose->mouse +mose->more, mouse, moslty->mostly mosture->moisture motiviated->motivated -mould->mold, mould, module +mould->mold, mould, module, mounth->month mouspointer->mousepointer movebackwrd->movebackward @@ -7283,7 +7272,7 @@ noveau->nouveau Novermber->November nowdays->nowadays nowe->now -nto->not, disable due to \n +nto->not, disabled due to \n nubmer->number nubmers->numbers nucular->nuclear @@ -9245,7 +9234,6 @@ sempahore->semaphore sempahores->semaphores senario->scenario senarios->scenarios -sence->sense sence->sense, since, sensistive->sensitive sensistively->sensitively @@ -9555,7 +9543,7 @@ specificed->specified specificiation->specification specificiations->specifications specificly->specifically -specificy->specify, specificity, specifically +specificy->specify, specificity, specifically, specifing->specifying specifiy->specify specifiying->specifying @@ -10167,7 +10155,7 @@ thikn->think thikness->thickness thikning->thinking, thickening, thikns->thinks -this this->this, this is, is this, +this this->this, this is, is this, thiunk->think thn->then thna->than @@ -10244,7 +10232,7 @@ tkaing->taking tlaking->talking to to->to, to do, tobbaco->tobacco -todays->today's, disable because of var names +todays->today's, disabled because of var names todya->today togehter->together toghether->together @@ -10890,9 +10878,9 @@ vulnerablility->vulnerability vyer->very vyre->very waht->what -wan't->want, wasn't +wan't->want, wasn't, wan->want -wan;t->want, wasn't +wan;t->want, wasn't, wanna->want to, disabled because one might want to allow informal spelling want's->wants want;s->wants diff --git a/codespell_lib/tests/test_basic.py b/codespell_lib/tests/test_basic.py index ef8e583a..5fa2fbe0 100644 --- a/codespell_lib/tests/test_basic.py +++ b/codespell_lib/tests/test_basic.py @@ -10,11 +10,12 @@ import sys import tempfile import warnings -from nose.tools import with_setup, assert_equal, assert_true +import pytest import codespell_lib as cs +@pytest.fixture(scope='function') def reload_codespell_lib(): try: reload # Python 2.7 @@ -37,63 +38,63 @@ def test_command(): """Test running the codespell executable""" # With no arguments does "." with TemporaryDirectory() as d: - assert_equal(run_codespell(cwd=d), 0) + assert run_codespell(cwd=d) == 0 with open(op.join(d, 'bad.txt'), 'w') as f: f.write('abandonned\nAbandonned\nABANDONNED\nAbAnDoNnEd') - assert_equal(run_codespell(cwd=d), 4) + assert run_codespell(cwd=d) == 4 def test_basic(): """Test some basic functionality""" - assert_equal(cs.main('_does_not_exist_'), 0) + assert cs.main('_does_not_exist_') == 0 with tempfile.NamedTemporaryFile('w') as f: pass with CaptureStdout() as sio: - assert_equal(cs.main('-D', 'foo', f.name), 1) # missing dictionary + assert cs.main('-D', 'foo', f.name) == 1, 'missing dictionary' try: - assert_true('cannot find dictionary' in sio[1]) - assert_equal(cs.main(f.name), 0) # empty file + assert 'cannot find dictionary' in sio[1] + assert cs.main(f.name) == 0, 'empty file' with open(f.name, 'a') as f: f.write('this is a test file\n') - assert_equal(cs.main(f.name), 0) # good + assert cs.main(f.name) == 0, 'good' with open(f.name, 'a') as f: f.write('abandonned\n') - assert_equal(cs.main(f.name), 1) # bad + assert cs.main(f.name) == 1, 'bad' with open(f.name, 'a') as f: f.write('abandonned\n') - assert_equal(cs.main(f.name), 2) # worse + assert cs.main(f.name) == 2, 'worse' finally: os.remove(f.name) with TemporaryDirectory() as d: with open(op.join(d, 'bad.txt'), 'w') as f: f.write('abandonned\nAbandonned\nABANDONNED\nAbAnDoNnEd') - assert_equal(cs.main(d), 4) + assert cs.main(d) == 4 with CaptureStdout() as sio: - assert_equal(cs.main('-w', d), 0) - assert_true('FIXED:' in sio[1]) + assert cs.main('-w', d) == 0 + assert 'FIXED:' in sio[1] with open(op.join(d, 'bad.txt')) as f: new_content = f.read() - assert_equal(cs.main(d), 0) - assert_equal(new_content, 'abandoned\nAbandoned\nABANDONED\nabandoned') + assert cs.main(d) == 0 + assert new_content == 'abandoned\nAbandoned\nABANDONED\nabandoned' with open(op.join(d, 'bad.txt'), 'w') as f: f.write('abandonned abandonned\n') - assert_equal(cs.main(d), 2) + assert cs.main(d) == 2 with CaptureStdout() as sio: - assert_equal(cs.main('-q', '16', '-w', d), 0) - assert_equal(sio[0], '') - assert_equal(cs.main(d), 0) + assert cs.main('-q', '16', '-w', d) == 0 + assert sio[0] == '' + assert cs.main(d) == 0 # empty directory os.mkdir(op.join(d, 'test')) - assert_equal(cs.main(d), 0) + assert cs.main(d) == 0 # hidden file with open(op.join(d, 'test.txt'), 'w') as f: f.write('abandonned\n') - assert_equal(cs.main(op.join(d, 'test.txt')), 1) + assert cs.main(op.join(d, 'test.txt')) == 1 os.rename(op.join(d, 'test.txt'), op.join(d, '.test.txt')) - assert_equal(cs.main(op.join(d, '.test.txt')), 0) + assert cs.main(op.join(d, '.test.txt')) == 0 def test_interactivity(): @@ -103,20 +104,20 @@ def test_interactivity(): with tempfile.NamedTemporaryFile('w') as f: pass try: - assert_equal(cs.main(f.name), 0) # empty file + assert cs.main(f.name) == 0, 'empty file' with open(f.name, 'w') as f: f.write('abandonned\n') - assert_equal(cs.main('-i', '-1', f.name), 1) # bad + assert cs.main('-i', '-1', f.name) == 1, 'bad' with FakeStdin('y\n'): - assert_equal(cs.main('-i', '3', f.name), 1) + assert cs.main('-i', '3', f.name) == 1 with CaptureStdout() as sio: with FakeStdin('n\n'): - assert_equal(cs.main('-w', '-i', '3', f.name), 0) - assert_true('==>' in sio[0]) + assert cs.main('-w', '-i', '3', f.name) == 0 + assert '==>' in sio[0] with CaptureStdout(): with FakeStdin('x\ny\n'): - assert_equal(cs.main('-w', '-i', '3', f.name), 0) - assert_equal(cs.main(f.name), 0) + assert cs.main('-w', '-i', '3', f.name) == 0 + assert cs.main(f.name) == 0 finally: os.remove(f.name) @@ -126,11 +127,11 @@ def test_interactivity(): try: with open(f.name, 'w') as f: f.write('abandonned\n') - assert_equal(cs.main(f.name), 1) + assert cs.main(f.name) == 1 with CaptureStdout(): with FakeStdin(' '): # blank input -> Y - assert_equal(cs.main('-w', '-i', '3', f.name), 0) - assert_equal(cs.main(f.name), 0) + assert cs.main('-w', '-i', '3', f.name) == 0 + assert cs.main(f.name) == 0 finally: os.remove(f.name) @@ -141,27 +142,27 @@ def test_interactivity(): with open(f.name, 'w') as f: f.write('ackward\n') - assert_equal(cs.main(f.name), 1) + assert cs.main(f.name) == 1 with CaptureStdout(): with FakeStdin(' \n'): # blank input -> nothing - assert_equal(cs.main('-w', '-i', '3', f.name), 0) - assert_equal(cs.main(f.name), 1) + assert cs.main('-w', '-i', '3', f.name) == 0 + assert cs.main(f.name) == 1 with CaptureStdout(): with FakeStdin('0\n'): # blank input -> nothing - assert_equal(cs.main('-w', '-i', '3', f.name), 0) - assert_equal(cs.main(f.name), 0) + assert cs.main('-w', '-i', '3', f.name) == 0 + assert cs.main(f.name) == 0 with open(f.name, 'r') as f_read: - assert_equal(f_read.read(), 'awkward\n') + assert f_read.read() == 'awkward\n' with open(f.name, 'w') as f: f.write('ackward\n') - assert_equal(cs.main(f.name), 1) + assert cs.main(f.name) == 1 with CaptureStdout() as sio: with FakeStdin('x\n1\n'): # blank input -> nothing - assert_equal(cs.main('-w', '-i', '3', f.name), 0) - assert_true('a valid option' in sio[0]) - assert_equal(cs.main(f.name), 0) + assert cs.main('-w', '-i', '3', f.name) == 0 + assert 'a valid option' in sio[0] + assert cs.main(f.name) == 0 with open(f.name, 'r') as f: - assert_equal(f.read(), 'backward\n') + assert f.read() == 'backward\n' finally: os.remove(f.name) @@ -174,26 +175,25 @@ def test_summary(): with CaptureStdout() as sio: cs.main(f.name) for ii in range(2): - assert_equal(sio[ii], '') # no output + assert sio[ii] == '', 'no output' with CaptureStdout() as sio: cs.main(f.name, '--summary') - assert_equal(sio[1], '') # stderr - assert_true('SUMMARY' in sio[0]) - assert_equal(len(sio[0].split('\n')), 5) # no output + assert sio[1] == '', 'stderr' + assert 'SUMMARY' in sio[0] + assert len(sio[0].split('\n')) == 5, 'no output' with open(f.name, 'w') as f: f.write('abandonned\nabandonned') with CaptureStdout() as sio: cs.main(f.name, '--summary') - assert_equal(sio[1], '') # stderr - assert_true('SUMMARY' in sio[0]) - assert_equal(len(sio[0].split('\n')), 7) - assert_true('abandonned' in sio[0].split()[-2]) + assert sio[1] == '', 'stderr' + assert 'SUMMARY' in sio[0] + assert len(sio[0].split('\n')) == 7 + assert 'abandonned' in sio[0].split()[-2] finally: os.remove(f.name) -@with_setup(reload_codespell_lib, reload_codespell_lib) -def test_ignore_dictionary(): +def test_ignore_dictionary(reload_codespell_lib): """Test ignore dictionary functionality""" with TemporaryDirectory() as d: with open(op.join(d, 'bad.txt'), 'w') as f: @@ -202,17 +202,16 @@ def test_ignore_dictionary(): pass with open(f.name, 'w') as f: f.write('abandonned\n') - assert_equal(cs.main('-I', f.name, d), 1) + assert cs.main('-I', f.name, d) == 1 -@with_setup(reload_codespell_lib, reload_codespell_lib) -def test_custom_regex(): +def test_custom_regex(reload_codespell_lib): """Test custom word regex""" with TemporaryDirectory() as d: with open(op.join(d, 'bad.txt'), 'w') as f: f.write('abandonned_abondon\n') - assert_equal(cs.main(d), 0) - assert_equal(cs.main('-r', "[a-z]+", d), 2) + assert cs.main(d) == 0 + assert cs.main('-r', "[a-z]+", d) == 2 def test_exclude_file(): @@ -220,13 +219,13 @@ def test_exclude_file(): with TemporaryDirectory() as d: with open(op.join(d, 'bad.txt'), 'wb') as f: f.write('abandonned 1\nabandonned 2\n'.encode('utf-8')) - assert_equal(cs.main(d), 2) + assert cs.main(d) == 2 with tempfile.NamedTemporaryFile('w') as f: pass with open(f.name, 'wb') as f: f.write('abandonned 1\n'.encode('utf-8')) - assert_equal(cs.main(d), 2) - assert_equal(cs.main('-x', f.name, d), 1) + assert cs.main(d) == 2 + assert cs.main('-x', f.name, d) == 1 def test_encoding(): @@ -235,24 +234,24 @@ def test_encoding(): with tempfile.NamedTemporaryFile('wb') as f: pass # with CaptureStdout() as sio: - assert_equal(cs.main(f.name), 0) + assert cs.main(f.name) == 0 try: with open(f.name, 'wb') as f: f.write(u'naïve\n'.encode('utf-8')) - assert_equal(cs.main(f.name), 0) - assert_equal(cs.main('-e', f.name), 0) + assert cs.main(f.name) == 0 + assert cs.main('-e', f.name) == 0 with open(f.name, 'ab') as f: f.write(u'naieve\n'.encode('utf-8')) - assert_equal(cs.main(f.name), 1) + assert cs.main(f.name) == 1 # Binary file warning with open(f.name, 'wb') as f: f.write(b'\x00\x00naiive\x00\x00') with CaptureStdout() as sio: - assert_equal(cs.main(f.name), 0) - assert_true('WARNING: Binary file' in sio[1]) + assert cs.main(f.name) == 0 + assert 'WARNING: Binary file' in sio[1] with CaptureStdout() as sio: - assert_equal(cs.main('-q', '2', f.name), 0) - assert_equal(sio[1], '') + assert cs.main('-q', '2', f.name) == 0 + assert sio[1] == '' finally: os.remove(f.name) @@ -262,20 +261,20 @@ def test_ignore(): with TemporaryDirectory() as d: with open(op.join(d, 'good.txt'), 'w') as f: f.write('this file is okay') - assert_equal(cs.main(d), 0) + assert cs.main(d) == 0 with open(op.join(d, 'bad.txt'), 'w') as f: f.write('abandonned') - assert_equal(cs.main(d), 1) - assert_equal(cs.main('--skip=bad*', d), 0) - assert_equal(cs.main('--skip=bad.txt', d), 0) + assert cs.main(d) == 1 + assert cs.main('--skip=bad*', d) == 0 + assert cs.main('--skip=bad.txt', d) == 0 subdir = op.join(d, 'ignoredir') os.mkdir(subdir) with open(op.join(subdir, 'bad.txt'), 'w') as f: f.write('abandonned') - assert_equal(cs.main(d), 2) - assert_equal(cs.main('--skip=bad*', d), 0) - assert_equal(cs.main('--skip=*ignoredir*', d), 1) - assert_equal(cs.main('--skip=ignoredir', d), 1) + assert cs.main(d) == 2 + assert cs.main('--skip=bad*', d) == 0 + assert cs.main('--skip=*ignoredir*', d) == 1 + assert cs.main('--skip=ignoredir', d) == 1 def test_check_filename(): @@ -283,7 +282,7 @@ def test_check_filename(): with TemporaryDirectory() as d: with open(op.join(d, 'abandonned.txt'), 'w') as f: f.write('.') - assert_equal(cs.main('-f', d), 1) + assert cs.main('-f', d) == 1 class TemporaryDirectory(object): @@ -385,3 +384,52 @@ def FakeStdin(text): yield finally: sys.stdin = oldin + + +def test_dictionary_formatting(): + """Test that all dictionary entries are in lower case and non-empty.""" + err_dict = dict() + with open(op.join(op.dirname(__file__), '..', 'data', + 'dictionary.txt'), 'rb') as fid: + for line in fid: + err, rep = line.decode('utf-8').split('->') + err = err.lower() + assert err not in err_dict, 'entry already exists' + rep = rep.rstrip('\n') + assert len(rep) > 0, 'corrections must be non-empty' + if rep.count(','): + if not rep.endswith(','): + assert 'disabled' in rep.split(',')[-1], \ + ('currently corrections must end with trailing "," (if' + ' multiple corrections are available) or ' + 'have "disabled" in the comment') + err_dict[err] = rep + reps = [r.strip() for r in rep.lower().split(',')] + reps = [r for r in reps if len(r)] + unique = list() + for r in reps: + if r not in unique: + unique.append(r) + assert reps == unique, 'entries are not (lower-case) unique' + + +def test_case_handling(reload_codespell_lib): + """Test that capitalized entries get detected properly.""" + # Some simple Unicode things + with tempfile.NamedTemporaryFile('wb') as f: + pass + # with CaptureStdout() as sio: + assert cs.main(f.name) == 0 + try: + with open(f.name, 'wb') as f: + f.write('this has an ACII error'.encode('utf-8')) + with CaptureStdout() as sio: + assert cs.main(f.name) == 1 + assert 'ASCII' in sio[0] + with CaptureStdout() as sio: + assert cs.main('-w', f.name) == 0 + assert 'FIXED' in sio[1] + with open(f.name, 'rb') as f: + assert f.read().decode('utf-8') == 'this has an ASCII error' + finally: + os.remove(f.name) diff --git a/setup.cfg b/setup.cfg index da17709e..d46869ca 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,8 +1,5 @@ -[nosetests] -with-coverage = 1 -cover-package = codespell_lib -detailed-errors = 1 -verbosity = 2 +[tool:pytest] +addopts = --cov=codespell_lib --showlocals -rs --cov-report= [flake8] exclude = build, ci-helpers