Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,8 @@ build_script:
- 'echo scons args: %sconsArgs%'
- py scons.py source %sconsArgs%
# We don't need launcher to run tests, so run the tests before launcher.
# We also build the translation template (pot) just to ensure it succeeds.
- py scons.py tests pot %sconsArgs%
# We don't actually need the pot as a build artifact though.
- py scons.py tests %sconsArgs%
# The pot gets built by tests, but we don't actually need it as a build artifact.
- del output\*.pot
- 'echo scons output targets: %sconsOutTargets%'
- py scons.py %sconsOutTargets% %sconsArgs%
Expand Down
12 changes: 9 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,20 +208,26 @@ scons launcher version=test1
## Running Automated Tests
If you make a change to the NVDA code, you should run NVDA's automated tests.
These tests help to ensure that code changes do not unintentionally break functionality that was previously working.
Currently, NVDA has only one kind of automated testing: unit tests.
Currently, NVDA has two kinds of automated testing: unit tests and translatable string checks.

To run the unit tests, first change directory to the root of the NVDA source distribution as above.
To run the tests, first change directory to the root of the NVDA source distribution as above.
Then, run:

```
scons tests
```

To run only specific tests, specify them using the `unitTests` variable on the command line.
To run only specific unit tests, specify them using the `unitTests` variable on the command line.
The tests should be provided as a comma separated list.
Each test should be specified as a Python module, class or method relative to the `tests\unit` directory.
For example, to run only methods in the `TestMove` and `TestSelection` classes in the file `tests\unit\test_cursorManager.py` file, run this command:

```
scons tests unitTests=test_cursorManager.TestMove,test_cursorManager.TestSelection
```

To run only the translatable string checks (which check that all translatable strings have translator comments), run:

```
scons checkPot
```
6 changes: 1 addition & 5 deletions sconstruct
Original file line number Diff line number Diff line change
Expand Up @@ -399,8 +399,4 @@ env.Alias("symbolsArchive", symbolsArchive)

env.Default(dist)

if "tests" in COMMAND_LINE_TARGETS:
exports=["env"])
env.Depends(target, sourceDir)
env.AlwaysBuild(target)
tests = env.Alias("tests", target)
env.SConscript("tests/sconscript", exports=["env", "sourceDir", "pot"])
9 changes: 8 additions & 1 deletion source/NVDAObjects/window/winword.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,8 @@ def label(self):
author=self.collectionItem.author
date=self.collectionItem.date
text=self.collectionItem.range.text
# Translators: The label shown for a comment in the NVDA Elements List dialog in Microsoft Word.
# {text}, {author} and {date} will be replaced by the corresponding details about the comment.
return _(u"comment: {text} by {author} on {date}").format(author=author,text=text,date=date)

def rangeFromCollectionItem(self,item):
Expand All @@ -380,6 +382,10 @@ def label(self):
date=self.collectionItem.date
description=self.collectionItem.formatDescription or ""
text=(self.collectionItem.range.text or "")[:100]
# Translators: The label shown for an editor revision (tracked change) in the NVDA Elements List dialog in Microsoft Word.
# {revisionType} will be replaced with the type of revision; e.g. insertion, deletion or property.
# {description} will be replaced with a description of the formatting changes, if any.
# {text}, {author} and {date} will be replaced by the corresponding details about the revision.
return _(u"{revisionType} {description}: {text} by {author} on {date}").format(revisionType=revisionType,author=author,text=text,date=date,description=description)

class WordDocumentSpellingErrorQuickNavItem(WordDocumentCollectionQuickNavItem):
Expand All @@ -390,7 +396,8 @@ def rangeFromCollectionItem(self,item):
@property
def label(self):
text=self.collectionItem.text
# Translators: the label for a speling error shown in NVDA's Elements List dialog for Microsoft Word
# Translators: The label shown for a spelling error in the NVDA Elements List dialog in Microsoft Word.
# {text} will be replaced with the text of the spelling error.
return _(u"spelling: {text}").format(text=text)

class WinWordCollectionQuicknavIterator(object):
Expand Down
192 changes: 192 additions & 0 deletions tests/checkPot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#tests/checkPot.py
#A part of NonVisual Desktop Access (NVDA)
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
#Copyright (C) 2017 NV Access Limited

"""Check a translation template (pot) for strings without translator comments.
"""

import sys

# Existing messages that we know don't have translator comments yet.
# Ideally, all of these should get translator comments,
# but this is not realistic right now.
# A message should be removed from here once a translator comment is added for it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the test fail if the exception is incorrectly specified? EG if someone adds a translator comment for one of these, and forgets to remove it from the exception list?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found out it does, could you update this comment to specify that it does?

EXPECTED_MESSAGES_WITHOUT_COMMENTS = {
'Focus mode',
'%s landmark',
'border',
'filler',
'line',
'data item',
'header item',
'calendar',
'video',
'audio',
'modal',
'iconified',
'editable',
'checkable',
'draggable',
'dragging',
'sorted',
'sorted ascending',
'sorted descending',
'gesture map File Error',
'text \\"%s\\" not found',
'Find Error',
'Selected %s',
'on',
'Type help(object) to get help about object.',
'Type exit() to exit the console',
'NVDA Python Console',
'Emulates pressing %s on the system keyboard',
'continuous section break',
'new column section break',
'new page section break',
'even pages section break',
'odd pages section break',
'column break',
'background pattern {pattern}',
'NVDA Speech Viewer',
'text mode',
'object mode',
'NonVisual Desktop Access',
'A free and open source screen reader for Microsoft Windows',
'Copyright (C) {years} NVDA Contributors',
'Display',
'Reports and moves the review cursor to a recent message',
'left',
'right',
'Show the Handy Tech driver configuration window.',
'Recognition failed',
'General settings',
'Change the synthesizer to be used',
'Choose the voice, rate, pitch and volume to use',
'Change reporting of mouse shape and object under mouse',
'Configure how and when the review cursor moves',
'Change reporting of objects',
'Change virtual buffers specific settings',
'Change settings of document properties',
'NVDA &web site',
'Reset all settings to saved state',
'Reset all settings to default state',
'Write the current configuration to nvda.ini',
'E&xit',
'Options',
'Error renaming profile.',
'Use this profile for:',
'This change requires administrator privileges.',
'Insufficient Privileges',
'Synthesizer Error',
'Dictionary Entry Error',
'Could not load the %s display.',
'Braille Display Error',
'word',
'Taskbar',
'%s items',
'invoke',
'Desktop',
'Input Message is {title}: {message}',
'Input Message is {message}',
'Series color: {colorName} ',
'Comments',
'Endnotes',
'Even pages footer',
'Even pages header',
'First page footer',
'First page header',
'Footnotes',
'Primary footer',
'Primary header',
'Text frame',
}

def checkPot(fileName):
"""Returns the number of errors.
Also prints error messages and a summary to standard output.
"""
errors = 0
expectedErrors = 0
unexpectedSuccesses = 0
with file(fileName, "rt") as pot:
for line in pot:
line = line.rstrip()
if not line:
# End of message.
hasComment = False
sourceLines = []
context = ""
continue
if line == 'msgid ""':
# Header.
continue
if line.startswith("#. Translators: "):
# Example: "#. Translators: a message reported in the SetRowHeader script for Microsoft Word."
hasComment = True
continue
if line.startswith("#: "):
# Example: "#: NVDAObjects\window\winword.py:1322"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused by this, is this saying go and look at winword.py line 1322 for an example, or is the path part of the example? If so (the path case) it might be handy to add some further explanation text. EG "Sometimes we include paths in the translator comments, and so we must handle these because..."

# Strip the "#: " prefix (3 chars).
sourceLines.append(line[3:])
continue
if line.startswith('msgctxt "'):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably assumed knowledge for this, but what are: msgctxt, msgid, msgstr. Are these concepts from some other library module? Either a brief explanation of what they are or a pointer to where to find out about them could be handy for someone who comes to maintain this without that background knowledge.

context = getStringFromLine(line)
continue
if line.startswith("msgid "):
# Get the message.
if line == 'msgid ""':
# Multi-line msgid.
# Subsequent lines are just quoted strings which should be concatenated.
msgid = ""
for line in pot:
if line.startswith("msgstr "):
break
msgid += getStringFromLine(line)
else:
# Single line msgid.
# Example: msgid "Secure Desktop"
msgid = getStringFromLine(line)
if context:
# The context must be considered as part of the message.
message = "[{context}] {msgid}".format(context=context, msgid=msgid)
else:
message = msgid
isExpectedError = message in EXPECTED_MESSAGES_WITHOUT_COMMENTS
if not hasComment and isExpectedError:
expectedErrors += 1
continue
if hasComment and isExpectedError:
error = ("Message has translator comment, but one wasn't expected.\n"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, turns out you do report this case. Great!

"This is good, but please remove from EXPECTED_MESSAGES_WITHOUT_COMMENTS in tests/checkPot.py")
unexpectedSuccesses += 1
elif not hasComment:
errors += 1
error = "Message has no translator comment."
else:
continue
print("{error}\n"
"Source lines: {lines}\n"
"Message: {message}\n"
.format(error=error, lines=" ".join(sourceLines), message=message))
continue
print("{errors} errors, {unexpectedSuccesses} unexpected successes, {expectedErrors} expected errors"
.format(errors=errors, unexpectedSuccesses=unexpectedSuccesses, expectedErrors=expectedErrors))
return errors + unexpectedSuccesses

def getStringFromLine(line):
if line.startswith('"'):
# The quoted string begins at the start of the line.
quoted = line
else:
# The quoted string starts after a command.
# Example: msgid "Secure Desktop"
quoted = line.split(" ", 1)[1]
# Strip the quotes.
return quoted[1:-1]

if __name__ == "__main__":
# Support command line usage for quick testing.
fileName = sys.argv[1]
print(checkPot(fileName))
40 changes: 40 additions & 0 deletions tests/sconscript
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
###
#This file is a part of the NVDA project.
#URL: http://www.nvaccess.org/
#Copyright 2017 NV Access Limited.
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License version 2.0, as published by
#the Free Software Foundation.
#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.
#This license can be found at:
#http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
###

import checkPot

Import("env", "sourceDir", "pot")

unitTests = env.SConscript("unit/sconscript", exports=["env"])
env.Depends(unitTests, sourceDir)
env.AlwaysBuild(unitTests)

def checkPotAction(target, source, env):
return checkPot.checkPot(source[0].abspath)
checkPotTarget = env.Command("checkPot", pot, checkPotAction)
env.Depends(checkPotTarget, pot)
env.AlwaysBuild(checkPotTarget)
env.Alias("checkPot", checkPotTarget)

# Determine the targets for scons tests.
# If specific tests are explicitly specified, only run those.
explicitUnitTests = env.get("unitTests")
explicitCheckPot = "checkPot" in COMMAND_LINE_TARGETS
explicit = explicitUnitTests or explicitCheckPot
tests = []
if not explicit or explicitUnitTests:
tests.append(unitTests)
if not explicit or explicitCheckPot:
tests.append(checkPotTarget)
env.Alias("tests", tests)
2 changes: 1 addition & 1 deletion tests/unit/sconscript
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import sys
Import("env")

cmd = [sys.executable, "-m", "unittest"]
tests = env["unitTests"]
tests = env.get("unitTests")
if tests:
# Run specific tests.
tests = tests.split(",")
Expand Down