Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON & XML formaters #442

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
81951b3
chore: ignore venv
QuentinN42 Feb 3, 2022
b858cbb
refactor: moved formater inside another file
QuentinN42 Feb 3, 2022
3601817
refactor: moved output at the end of the tests
QuentinN42 Feb 3, 2022
46a12a8
fix: missed return statement
QuentinN42 Feb 3, 2022
c4463c7
feat: max_level as separate function
QuentinN42 Feb 3, 2022
6217241
refactor: use object inheritance for the formaters
QuentinN42 Feb 3, 2022
fbf8108
feat: json formater
QuentinN42 Feb 3, 2022
805b9a9
feat: junitxml formater
QuentinN42 Feb 3, 2022
6a06e8d
feat: changed json output file name to path
QuentinN42 Feb 5, 2022
2839489
typo
QuentinN42 Feb 5, 2022
d24519c
feat: add codeclimate output
QuentinN42 Feb 5, 2022
4baaf12
feat: add message to the json format
QuentinN42 Feb 5, 2022
e04f03a
feat: auto infer formats
QuentinN42 Feb 5, 2022
4057451
feat: changed json format according to @adrienverge comment
QuentinN42 Feb 8, 2022
dab7a80
fix: test_run_with_user_global_config_file patched
QuentinN42 Feb 10, 2022
b1ce2f2
test: escape_xml
QuentinN42 Feb 10, 2022
32de447
test: severity_from_level
QuentinN42 Feb 10, 2022
eb48e1d
test: Base class test
QuentinN42 Feb 10, 2022
1f6adf6
test: Class to test all fmt
QuentinN42 Feb 10, 2022
b42ac81
chore: rm coverage
QuentinN42 Feb 10, 2022
d666308
tests: use ddt to parametrize tests
QuentinN42 Feb 10, 2022
4ae5487
feat: add the possibility to pass the level as argument
QuentinN42 Feb 10, 2022
418952c
refactor: match flake requirements
QuentinN42 Feb 10, 2022
48b5e2b
tests: tested formats with errors
QuentinN42 Feb 10, 2022
e018da1
feat: fixed some isues
QuentinN42 Feb 10, 2022
0354c33
ci: add ddt to the tests requirements and update the contributing fil…
QuentinN42 Feb 11, 2022
3561a77
tests: testing skipping warn with None level
QuentinN42 Feb 11, 2022
64e19e8
fix: filter None to exclude them
QuentinN42 Feb 11, 2022
f0000a1
test: test with mixed errors / files
QuentinN42 Feb 11, 2022
fba5b70
tests: use ddt to others classes
QuentinN42 Feb 11, 2022
d7f59fe
test: escape real text
QuentinN42 Feb 11, 2022
ad46f33
test: remaining tests done
QuentinN42 Feb 11, 2022
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Append GitHub Actions system path
run: echo "$HOME/.local/bin" >> $GITHUB_PATH
- run: pip install coveralls
- run: pip install coveralls ddt
Copy link
Author

Choose a reason for hiding this comment

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

I've added this module to make pytest.mark.parametrized like tests.
I don't konw if there is a built in unittest solution for that.

- run: pip install .
- run: coverage run --source=yamllint -m unittest discover
- name: Coveralls
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ __pycache__
/yamllint.egg-info
/build
/.eggs
.venv
venv
.coverage
coverage.xml
8 changes: 6 additions & 2 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ Pull Request Process
.. code:: bash

pip install --user .
python -m unittest discover # all tests...
python -m unittest tests/rules/test_commas.py # or just some tests (faster)
pip install coveralls ddt
# all tests...
python -m coverage run --source=yamllint -m unittest discover
coverage report
# or just some tests (faster)
python -m unittest tests/rules/test_commas.py
Comment on lines +17 to +22
Copy link
Author

Choose a reason for hiding this comment

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

Updated the docs to show coverage when running tests.


3. If you add code that should be tested, add tests.

Expand Down
3 changes: 3 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,9 @@ def test_run_with_user_global_config_file(self):
config = os.path.join(dir, 'config')

self.addCleanup(os.environ.update, HOME=os.environ['HOME'])
# remove other env vars to make sure we are using the HOME config file.
os.environ.pop('YAMLLINT_CONFIG_FILE', None)
os.environ.pop('XDG_CONFIG_HOME', None)
Comment on lines +308 to +310
Copy link
Author

Choose a reason for hiding this comment

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

I've also patched the test_run_with_user_global_config_file test by adding removing other env vars.

os.environ['HOME'] = home

with open(config, 'w') as f:
Expand Down
270 changes: 270 additions & 0 deletions tests/test_format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
# -*- coding: utf-8 -*-
# flake8: noqa
import unittest
import string
import ddt

from yamllint.linter import LintProblem
from yamllint.format import (
escape_xml,
severity_from_level,
max_level,
Formater,
ParsableFormater,
GithubFormater,
ColoredFormater,
StandardFormater,
JSONFormater,
JunitFormater,
CodeclimateFormater
)


@ddt.ddt
class TextToXMLTestCase(unittest.TestCase):

def test_letters_chars(self):
txt = string.ascii_letters
self.assertEqual(escape_xml(txt), txt)

@ddt.data(
('&', '&'),
('<', '&lt;'),
('>', '&gt;'),
('"', '&quot;'),
("'", '&apos;'),
("too many blank lines (3 > 2)", 'too many blank lines (3 &gt; 2)'),
('line too long (100 > 80 characters)', 'line too long (100 &gt; 80 characters)')
)
@ddt.unpack
def test_specials_chars(self, inp, out):
self.assertEqual(escape_xml(inp), out)


@ddt.ddt
class CodeClimateSeverityTestCase(unittest.TestCase):

@ddt.data(
(None, "info"),
('warning', "minor"),
('error', "major"),
(0, "info"),
(1, "minor"),
(2, "major"),
)
@ddt.unpack
def test_specials_chars(self, inp, out):
self.assertEqual(severity_from_level(inp), out)


@ddt.ddt
class FormaterTestCase(unittest.TestCase):

def test_get_formaters_names(self):
self.assertEqual(
set(Formater.get_formaters_names()),
{
"parsable",
"github",
"colored",
"standard",
"json",
"junitxml",
"codeclimate"
}
)

@ddt.data(
("parsable", ParsableFormater, True),
("github", GithubFormater, True),
("colored", ColoredFormater, True),
("standard", StandardFormater, True),
("json", JSONFormater, True),
("junitxml", JunitFormater, True),
("codeclimate", CodeclimateFormater, True),
("parsable", ParsableFormater, False),
("github", GithubFormater, False),
("colored", ColoredFormater, False),
("standard", StandardFormater, False),
("json", JSONFormater, False),
("junitxml", JunitFormater, False),
("codeclimate", CodeclimateFormater, False),
)
@ddt.unpack
def test_get_formater(self, name, cls, no_warn):
res = Formater.get_formater(name, no_warn)
self.assertTrue(isinstance(res, cls))
self.assertEqual(res.no_warn, no_warn)

def test_unknown_formater(self):
with self.assertRaises(ValueError):
Formater.get_formater("unknown", False)

def test_abstract_class(self):
inst = Formater(False)
with self.assertRaises(NotImplementedError):
inst.show_problems_for_all_files([])
with self.assertRaises(NotImplementedError):
inst.show_problems_for_file([], "a")
with self.assertRaises(NotImplementedError):
inst.show_problem(None, "a")


NONE = {}
NO_ERROR = {"file1.yml": []}
ONE_NOTHING = {"file1.yml": [
LintProblem(1, 1, desc="desc of None", rule="my-rule")
]}
ONE_ERROR = {"file1.yml": [
LintProblem(
line=1,
column=2,
desc="desc of error",
rule="my-rule",
level="error"
)
]}
ONE_WARNING = {"file1.yml": [
LintProblem(
line=1,
column=2,
desc="desc of warn",
rule="my-rule",
level="warning"
)
]}
MIXED_ONE_FILE = {"file1.yml": [
ONE_NOTHING["file1.yml"][0],
ONE_ERROR["file1.yml"][0],
ONE_WARNING["file1.yml"][0]
]}
MIXED_MULT_FILE = {
"file1.yml": ONE_NOTHING["file1.yml"],
"file2.yml": ONE_ERROR["file1.yml"],
"file3.yml": ONE_WARNING["file1.yml"]
}


@ddt.ddt
class FormatersTestCase(unittest.TestCase):

@ddt.data(
# No errors
(ParsableFormater(True), NONE, ""),
(GithubFormater(True), NONE, ""),
(ColoredFormater(True), NONE, ""),
(StandardFormater(True), NONE, ""),
(JSONFormater(True), NONE, "[]\n"),
(CodeclimateFormater(True), NONE, "[]\n"),
(ParsableFormater(True), NO_ERROR, ""),
(GithubFormater(True), NO_ERROR, ""),
(ColoredFormater(True), NO_ERROR, ""),
(StandardFormater(True), NO_ERROR, ""),
(JSONFormater(True), NO_ERROR, "[]\n"),
(CodeclimateFormater(True), NO_ERROR, "[]\n"),
(ParsableFormater(True), ONE_NOTHING, ""),
(GithubFormater(True), ONE_NOTHING, ""),
(ColoredFormater(True), ONE_NOTHING, ""),
(StandardFormater(True), ONE_NOTHING, ""),
(JSONFormater(True), ONE_NOTHING, '[]\n'),
(CodeclimateFormater(True), ONE_NOTHING, '[]\n'),
# Errors with no level are ignored
(ParsableFormater(False), ONE_NOTHING, ""),
(GithubFormater(False), ONE_NOTHING, ""),
(ColoredFormater(False), ONE_NOTHING, ""),
(StandardFormater(False), ONE_NOTHING, ""),
(JSONFormater(False), ONE_NOTHING, '[]\n'),
(CodeclimateFormater(False), ONE_NOTHING, '[]\n'),
# 1 Skipped warning
(ParsableFormater(True), ONE_WARNING, ""),
(GithubFormater(True), ONE_WARNING, ""),
(ColoredFormater(True), ONE_WARNING, ""),
(StandardFormater(True), ONE_WARNING, ""),
(JSONFormater(True), ONE_WARNING, '[]\n'),
(CodeclimateFormater(True), ONE_WARNING, '[]\n'),
# 1 Unskipped warning
(ParsableFormater(False), ONE_WARNING, 'file1.yml:1:2: [warning] desc of warn (my-rule)\n'),
(GithubFormater(False), ONE_WARNING, '::group::file1.yml\n::warning file=file1.yml,line=1,col=2::1:2 [my-rule] desc of warn\n::endgroup::\n\n'),
(ColoredFormater(False), ONE_WARNING, '\x1b[4mfile1.yml\x1b[0m\n \x1b[2m1:2\x1b[0m \x1b[33mwarning\x1b[0m desc of warn \x1b[2m(my-rule)\x1b[0m\n\n'),
(StandardFormater(False), ONE_WARNING, 'file1.yml\n 1:2 warning desc of warn (my-rule)\n\n'),
(JSONFormater(False), ONE_WARNING, '[\n {\n "line": 1,\n "column": 2,\n "rule": "my-rule",\n "level": "warning",\n "message": "desc of warn",\n "path": "file1.yml"\n }\n]\n'),
(CodeclimateFormater(False), ONE_WARNING, '[\n {\n "type": "issue",\n "check_name": "my-rule",\n "description": "desc of warn",\n "content": "desc of warn (my-rule)",\n "categories": [\n "Style"\n ],\n "location": {\n "path": "file1.yml",\n "positions": {\n "begin": {\n "line": 1,\n "column": 2\n }\n }\n },\n "remediation_points": 1000,\n "severity": "minor"\n }\n]\n'),
# 1 Error
(ParsableFormater(True), ONE_ERROR, 'file1.yml:1:2: [error] desc of error (my-rule)\n'),
(GithubFormater(True), ONE_ERROR, '::group::file1.yml\n::error file=file1.yml,line=1,col=2::1:2 [my-rule] desc of error\n::endgroup::\n\n'),
(ColoredFormater(True), ONE_ERROR, '\x1b[4mfile1.yml\x1b[0m\n \x1b[2m1:2\x1b[0m \x1b[31merror\x1b[0m desc of error \x1b[2m(my-rule)\x1b[0m\n\n'),
(StandardFormater(True), ONE_ERROR, 'file1.yml\n 1:2 error desc of error (my-rule)\n\n'),
(JSONFormater(True), ONE_ERROR, '[\n {\n "line": 1,\n "column": 2,\n "rule": "my-rule",\n "level": "error",\n "message": "desc of error",\n "path": "file1.yml"\n }\n]\n'),
(CodeclimateFormater(True), ONE_ERROR, '[\n {\n "type": "issue",\n "check_name": "my-rule",\n "description": "desc of error",\n "content": "desc of error (my-rule)",\n "categories": [\n "Style"\n ],\n "location": {\n "path": "file1.yml",\n "positions": {\n "begin": {\n "line": 1,\n "column": 2\n }\n }\n },\n "remediation_points": 1000,\n "severity": "major"\n }\n]\n'),
# mixed warn / err on the same file
(ParsableFormater(False), MIXED_ONE_FILE, 'file1.yml:1:2: [error] desc of error (my-rule)\nfile1.yml:1:2: [warning] desc of warn (my-rule)\n'),
(GithubFormater(False), MIXED_ONE_FILE, '::group::file1.yml\n::error file=file1.yml,line=1,col=2::1:2 [my-rule] desc of error\n::warning file=file1.yml,line=1,col=2::1:2 [my-rule] desc of warn\n::endgroup::\n\n'),
(ColoredFormater(False), MIXED_ONE_FILE, '\x1b[4mfile1.yml\x1b[0m\n \x1b[2m1:2\x1b[0m \x1b[31merror\x1b[0m desc of error \x1b[2m(my-rule)\x1b[0m\n \x1b[2m1:2\x1b[0m \x1b[33mwarning\x1b[0m desc of warn \x1b[2m(my-rule)\x1b[0m\n\n'),
(StandardFormater(False), MIXED_ONE_FILE, 'file1.yml\n 1:2 error desc of error (my-rule)\n 1:2 warning desc of warn (my-rule)\n\n'),
(JSONFormater(False), MIXED_ONE_FILE, '[\n {\n "line": 1,\n "column": 2,\n "rule": "my-rule",\n "level": "error",\n "message": "desc of error",\n "path": "file1.yml"\n },\n {\n "line": 1,\n "column": 2,\n "rule": "my-rule",\n "level": "warning",\n "message": "desc of warn",\n "path": "file1.yml"\n }\n]\n'),
(CodeclimateFormater(False), MIXED_ONE_FILE, '[\n {\n "type": "issue",\n "check_name": "my-rule",\n "description": "desc of error",\n "content": "desc of error (my-rule)",\n "categories": [\n "Style"\n ],\n "location": {\n "path": "file1.yml",\n "positions": {\n "begin": {\n "line": 1,\n "column": 2\n }\n }\n },\n "remediation_points": 1000,\n "severity": "major"\n },\n {\n "type": "issue",\n "check_name": "my-rule",\n "description": "desc of warn",\n "content": "desc of warn (my-rule)",\n "categories": [\n "Style"\n ],\n "location": {\n "path": "file1.yml",\n "positions": {\n "begin": {\n "line": 1,\n "column": 2\n }\n }\n },\n "remediation_points": 1000,\n "severity": "minor"\n }\n]\n'),
# mixed warn / err on multiples files
(ParsableFormater(False), MIXED_MULT_FILE, 'file2.yml:1:2: [error] desc of error (my-rule)\nfile3.yml:1:2: [warning] desc of warn (my-rule)\n'),
(GithubFormater(False), MIXED_MULT_FILE, '::group::file2.yml\n::error file=file2.yml,line=1,col=2::1:2 [my-rule] desc of error\n::endgroup::\n\n::group::file3.yml\n::warning file=file3.yml,line=1,col=2::1:2 [my-rule] desc of warn\n::endgroup::\n\n'),
(ColoredFormater(False), MIXED_MULT_FILE, '\x1b[4mfile2.yml\x1b[0m\n \x1b[2m1:2\x1b[0m \x1b[31merror\x1b[0m desc of error \x1b[2m(my-rule)\x1b[0m\n\n\x1b[4mfile3.yml\x1b[0m\n \x1b[2m1:2\x1b[0m \x1b[33mwarning\x1b[0m desc of warn \x1b[2m(my-rule)\x1b[0m\n\n'),
(StandardFormater(False), MIXED_MULT_FILE, 'file2.yml\n 1:2 error desc of error (my-rule)\n\nfile3.yml\n 1:2 warning desc of warn (my-rule)\n\n'),
(JSONFormater(False), MIXED_MULT_FILE, '[\n {\n "line": 1,\n "column": 2,\n "rule": "my-rule",\n "level": "error",\n "message": "desc of error",\n "path": "file2.yml"\n },\n {\n "line": 1,\n "column": 2,\n "rule": "my-rule",\n "level": "warning",\n "message": "desc of warn",\n "path": "file3.yml"\n }\n]\n'),
(CodeclimateFormater(False), MIXED_MULT_FILE, '[\n {\n "type": "issue",\n "check_name": "my-rule",\n "description": "desc of error",\n "content": "desc of error (my-rule)",\n "categories": [\n "Style"\n ],\n "location": {\n "path": "file2.yml",\n "positions": {\n "begin": {\n "line": 1,\n "column": 2\n }\n }\n },\n "remediation_points": 1000,\n "severity": "major"\n },\n {\n "type": "issue",\n "check_name": "my-rule",\n "description": "desc of warn",\n "content": "desc of warn (my-rule)",\n "categories": [\n "Style"\n ],\n "location": {\n "path": "file3.yml",\n "positions": {\n "begin": {\n "line": 1,\n "column": 2\n }\n }\n },\n "remediation_points": 1000,\n "severity": "minor"\n }\n]\n'),
)
@ddt.unpack
def test_all_formaters(self, inst, inp, ret):
self.assertEqual(
inst.show_problems_for_all_files(inp),
ret
)


@ddt.ddt
class MaxLevelTestCase(unittest.TestCase):

@ddt.data(
(NONE, 0),
(NO_ERROR, 0),
(ONE_NOTHING, 0),
(ONE_ERROR, 2),
(ONE_WARNING, 1),
(MIXED_ONE_FILE, 2),
(MIXED_MULT_FILE, 2),
)
@ddt.unpack
def test_all_formaters(self, inp, ret):
self.assertEqual(max_level(inp), ret)

@ddt.ddt
class JunitTestCase(unittest.TestCase):

@ddt.data(
(NONE, False, [], ['<\/error><\/testcase>', '<\/failure><\/testcase>'], 7),
(NO_ERROR, False, [], ['<\/error><\/testcase>', '<\/failure><\/testcase>'], 7),
(ONE_NOTHING, False, [], ['<\/error><\/testcase>', '<\/failure><\/testcase>'], 7),
(ONE_ERROR, False, ['<\/error><\/testcase>'], ['<\/failure><\/testcase>'], 7),
(ONE_WARNING, False, ['<\/failure><\/testcase>'], ['<\/error><\/testcase>'], 7),
(ONE_WARNING, True, [], ['<\/error><\/testcase>', '<\/failure><\/testcase>'], 7),
(MIXED_ONE_FILE, False, ['<\/error><\/testcase>', '<\/failure><\/testcase>'], [], 8),
(MIXED_MULT_FILE, False, ['<\/error><\/testcase>', '<\/failure><\/testcase>'], [], 8),
)
@ddt.unpack
def test_all_formaters(self, inp, no_warn, contain, not_contain, length):
res = JunitFormater(no_warn).show_problems_for_all_files(inp)
self.assertTrue(res.startswith(
'<?xml version="1.0" encoding="utf-8"?>\n' \
'<testsuites>\n' \
' <testsuite name="yamllint" '
))

for e in contain:
self.assertTrue(e in res)
for e in not_contain:
self.assertFalse(e in res)

self.assertTrue(res.endswith(
'\n' \
' </testsuite>\n' \
'</testsuites>\n'))

self.assertEqual(len(res.split('\n')), length)
Loading