
When there are multiple sonarpedia.json files for the same language, the rules picked up for the next sonarpedia.json file will override the ones picked up previously. With this fix, the rules are not overwritten but all are collected, so one repository can have multiple sonarpedia.json files for the same language.
202 lines
9.4 KiB
Python
202 lines
9.4 KiB
Python
import os
|
|
import shutil
|
|
import pytest
|
|
from git import Repo
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
from unittest.mock import (patch, PropertyMock)
|
|
from contextlib import contextmanager
|
|
|
|
from rspec_tools.coverage import (update_coverage_for_all_repos,
|
|
update_coverage_for_repo,
|
|
update_coverage_for_repo_version)
|
|
from rspec_tools.utils import (load_json, pushd)
|
|
|
|
def clear_working_dir(repo_dir):
|
|
for f in os.listdir(repo_dir):
|
|
if f != '.git':
|
|
if os.path.isdir(repo_dir / f):
|
|
shutil.rmtree(repo_dir / f)
|
|
else:
|
|
os.remove(repo_dir / f)
|
|
|
|
@pytest.fixture
|
|
def rules_dir():
|
|
return Path(__file__).parent.joinpath('resources', 'rules')
|
|
|
|
JSTS_SONARPEDIA='{"rules-metadata-path": "rules", "languages":["JS", "TS"]}'
|
|
MOCK_REPOS=[{'name':'SonarJS',
|
|
'versions': [
|
|
{'name': '3.3.0.5702',
|
|
'date': '2020-03-03 10:00:00',
|
|
'files': [['sonarpedia.json', JSTS_SONARPEDIA],
|
|
['rules/Sonar_way_profile.json', '{}'],
|
|
# MethodName is a lagacy key for S100
|
|
['rules/MethodName.json', '{}'], ['rules/S1145.json', '{}'],
|
|
# not in the rules directory, so not a rule:
|
|
['S200.json', '{}']]},
|
|
{'name': '5.0.0.6962',
|
|
'date': '2020-05-01 10:00:00',
|
|
'files': [['sonarpedia.json', JSTS_SONARPEDIA],
|
|
['rules/Sonar_way_profile.json', '{}'],
|
|
['rules/S100.json', '{}'], ['rules/S1145.json', '{}'], ['rules/S1192.json', '{}']]},
|
|
{'name': '6.7.0.14237',
|
|
'date': '2020-06-07 10:00:00',
|
|
'files': [['sonarpedia.json', JSTS_SONARPEDIA],
|
|
['rules/Sonar_way_profile.json', '{}'],
|
|
['rules/S100.json', '{}'], ['rules/S1145.json', '{}'], ['rules/S1192.json', '{}']]},
|
|
{'name': '7.0.0.14528',
|
|
'date': '2020-07-01 10:00:00',
|
|
'files': [['sonarpedia.json', JSTS_SONARPEDIA],
|
|
['rules/Sonar_way_profile.json', '{}'],
|
|
['rules/S100.json', '{}'], ['rules/S1192.json', '{}'],
|
|
# add a CSS analyzer in this version
|
|
['css-plugin/sonarpedia.json', '{"rules-metadata-path":"cssr", "languages":["CSS"]}'],
|
|
['css-plugin/cssr/S100.json', '{}'],
|
|
# add a XML analyzer here too
|
|
['xml/sonarpedia.json', '{"rules-metadata-path":"r", "languages":["XML"]}'],
|
|
['xml/r/S1000.json', '{}']]},
|
|
]},
|
|
{'name':'sonar-xml',
|
|
'versions': [
|
|
{'name': '1.2.0.1342',
|
|
'date': '2020-01-02 10:00:00',
|
|
'files': [['rules/Sonar_way_profile.json', '{}'],
|
|
['sonarpedia.json', '{"rules-metadata-path": "rules", "languages":["XML"]}'],
|
|
['rules/S103.json', '{}']]}
|
|
]},
|
|
{'name':'sonar-java',
|
|
'versions': [
|
|
{'name': '1.2.0.123',
|
|
'date': '2020-01-02 10:00:00',
|
|
'files': [['module1/rules/Sonar_way_profile.json', '{}'],
|
|
['module1/sonarpedia.json', '{"rules-metadata-path": "rules", "languages":["JAVA"]}'],
|
|
['module1/rules/S100.json', '{}'],
|
|
['module2/rules/Sonar_way_profile.json', '{}'],
|
|
['module2/sonarpedia.json', '{"rules-metadata-path": "rules", "languages":["JAVA"]}'],
|
|
['module2/rules/S101.json', '{}']]}
|
|
]},
|
|
{'name':'broken',
|
|
'versions': [
|
|
{'name': 'v1',
|
|
'date': '2020-01-01 01:01:01',
|
|
'files': [['sonarpedia.json', 'non-json'], # sonarpedia is non-json
|
|
['rules/S100.json', '{}']]}
|
|
]}]
|
|
|
|
def test_mock_tags_are_sorted_chronologically():
|
|
for mock_spec in MOCK_REPOS:
|
|
prev_date = datetime.strptime('1970-01-01 00:00:01', '%Y-%m-%d %H:%M:%S')
|
|
for tag in mock_spec['versions']:
|
|
cur_date = datetime.strptime(tag['date'], '%Y-%m-%d %H:%M:%S')
|
|
assert prev_date < cur_date
|
|
prev_date = cur_date
|
|
|
|
@pytest.fixture
|
|
def mock_git_analyzer_repos(tmpdir):
|
|
mocked_repos = {};
|
|
for mock_spec in MOCK_REPOS:
|
|
repo_name = mock_spec['name']
|
|
repo_dir = tmpdir.mkdir('mock-' + repo_name)
|
|
repo = Repo.init(str(repo_dir))
|
|
with repo.config_writer() as config_writer:
|
|
config_writer.set_value('user', 'name', 'originuser')
|
|
config_writer.set_value('user', 'email', 'originuser@mock.mock')
|
|
for tag in mock_spec['versions']:
|
|
clear_working_dir(repo_dir)
|
|
for fname, contents in tag['files']:
|
|
path = Path(repo_dir / fname)
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
with open(path, 'x') as f:
|
|
f.write(contents)
|
|
repo.git.add('--all')
|
|
repo.index.commit(f"adding version {tag['name']}", commit_date=tag['date'], author_date=tag['date'])
|
|
repo.create_tag(tag['name'])
|
|
mocked_repos[repo_name] = repo
|
|
def mock_clone_repo(repo_url, repo_name):
|
|
return mocked_repos[repo_url.split('/')[-1]]
|
|
def precloned_repo(x):
|
|
Repo(x)
|
|
mock=PropertyMock(side_effect=precloned_repo)
|
|
mock.clone_from=PropertyMock(side_effect=mock_clone_repo)
|
|
return mock
|
|
|
|
def test_update_coverage_for_repo_version(tmpdir, rules_dir: Path, mock_git_analyzer_repos):
|
|
with pushd(tmpdir), patch('rspec_tools.coverage.Repo', mock_git_analyzer_repos):
|
|
VER = '3.3.0.5702'
|
|
REPO = 'SonarJS'
|
|
update_coverage_for_repo_version(REPO, VER, rules_dir)
|
|
coverage = tmpdir.join('covered_rules.json')
|
|
assert coverage.exists()
|
|
cov = load_json(coverage)
|
|
assert 'JAVASCRIPT' in cov
|
|
assert 'S100' in cov['JAVASCRIPT']
|
|
assert 'MethodName' not in cov['JAVASCRIPT'] # MethodName is a legacy key for S100
|
|
assert 'S200' not in cov['JAVASCRIPT'] # S200.json is not in the rules directory in mock
|
|
assert cov['JAVASCRIPT']['S100'] == {'since': REPO + ' ' + VER, 'until': REPO + ' ' + VER}
|
|
|
|
# Running it again changes nothing
|
|
update_coverage_for_repo_version(REPO, VER, rules_dir)
|
|
assert cov == load_json(coverage)
|
|
|
|
# Running it for a newer version doesn't change when the rules are first implemented
|
|
VER2 = '5.0.0.6962'
|
|
update_coverage_for_repo_version(REPO, VER2, rules_dir)
|
|
cov_new = load_json(coverage)
|
|
assert set(cov['JAVASCRIPT'].keys()).issubset(set(cov_new['JAVASCRIPT'].keys()))
|
|
assert cov_new['JAVASCRIPT']['S100']['since'] == REPO + ' ' + VER
|
|
assert cov_new['JAVASCRIPT']['S100']['until'] == REPO + ' ' + VER2
|
|
assert cov_new['JAVASCRIPT']['S1192']['since'] == REPO + ' ' + VER2
|
|
assert cov_new['JAVASCRIPT']['S1192']['until'] == REPO + ' ' + VER2
|
|
|
|
# For rules supported on master only the 'since' part is kept
|
|
update_coverage_for_repo_version(REPO, 'master', rules_dir)
|
|
assert load_json(coverage)['JAVASCRIPT']['S100'] == REPO + ' ' + VER
|
|
|
|
|
|
def test_update_coverage_for_repo(tmpdir, rules_dir: Path, mock_git_analyzer_repos):
|
|
with pushd(tmpdir), patch('rspec_tools.coverage.Repo', mock_git_analyzer_repos):
|
|
REPO = 'SonarJS'
|
|
update_coverage_for_repo(REPO, rules_dir)
|
|
coverage = tmpdir.join('covered_rules.json')
|
|
assert coverage.exists()
|
|
cov = load_json(coverage)
|
|
assert 'JAVASCRIPT' in cov
|
|
assert 'TYPESCRIPT' in cov
|
|
assert 'S100' in cov['JAVASCRIPT']
|
|
assert 'MethodName' not in cov['JAVASCRIPT'] # MethodName is a legacy key for S100
|
|
assert cov['JAVASCRIPT']['S100'] == REPO + ' 3.3.0.5702'
|
|
assert 'S1145' in cov['JAVASCRIPT']
|
|
assert cov['JAVASCRIPT']['S1145'] == {'since': REPO + ' 3.3.0.5702', 'until': REPO + ' 6.7.0.14237'}
|
|
|
|
|
|
@patch('rspec_tools.coverage.REPOS', ['SonarJS', 'sonar-xml', 'sonar-java'])
|
|
def test_update_coverage_for_all_repos(tmpdir, rules_dir: Path, mock_git_analyzer_repos):
|
|
with pushd(tmpdir), patch('rspec_tools.coverage.Repo', mock_git_analyzer_repos):
|
|
update_coverage_for_all_repos(rules_dir)
|
|
coverage = tmpdir.join('covered_rules.json')
|
|
assert coverage.exists()
|
|
cov = load_json(coverage)
|
|
assert {'JAVASCRIPT', 'TYPESCRIPT', 'XML', 'CSS', 'JAVA'} == set(cov.keys())
|
|
assert 'S100' in cov['JAVASCRIPT']
|
|
assert 'MethodName' not in cov['JAVASCRIPT'] # MethodName is a legacy key for S100
|
|
assert {'S100'} == set(cov['CSS'].keys())
|
|
assert {'S103', 'S1000'} == set(cov['XML'].keys())
|
|
assert cov['XML']['S1000'] == 'SonarJS 7.0.0.14528'
|
|
assert {'S100', 'S101'} == set(cov['JAVA'].keys())
|
|
|
|
def test_update_coverage_no_sonarpedia(tmpdir, rules_dir: Path, mock_git_analyzer_repos, capsys):
|
|
with pushd(tmpdir), patch('rspec_tools.coverage.Repo', mock_git_analyzer_repos):
|
|
update_coverage_for_repo_version('broken', 'v1', rules_dir)
|
|
assert 'failed to collect implemented rules for' in capsys.readouterr().out
|
|
coverage = tmpdir.join('covered_rules.json')
|
|
assert coverage.exists()
|
|
cov = load_json(coverage)
|
|
assert cov == {}
|
|
|
|
def test_update_coverage_nonexisting_versio(tmpdir, rules_dir: Path, mock_git_analyzer_repos, capsys):
|
|
with pushd(tmpdir), patch('rspec_tools.coverage.Repo', mock_git_analyzer_repos):
|
|
with pytest.raises(Exception):
|
|
update_coverage_for_repo_version('broken', 'non-existing', rules_dir)
|
|
assert 'checkout failed' in capsys.readouterr().out
|