#!/usr/bin/python3 import subprocess from pathlib import Path import tempfile import os def set_git_identity(): """Set local git identity, but only in local repo (to not overwrite user settings)""" subprocess.run(['git', 'config', 'user.name', 'James Bond']) subprocess.run(['git', 'config', 'user.email', '007@sis.gov.uk']) def create_file(filename: str, file_content: str): """Create a file in the current directory and adds it to git""" Path(filename).parent.mkdir(parents=True, exist_ok=True) with open(filename, "w") as textfile: print(file_content, file=textfile) subprocess.run(['git', 'add', filename], check=True) class ScriptTest(): def __init__(self, scriptpath: Path): self.scriptpath = Path(scriptpath) def __enter__(self): """Create temporary, minimal test environment, and change to it""" self.lmms_tmpdir = tempfile.TemporaryDirectory(dir='.') os.chdir(self.lmms_tmpdir.name) # prerequirements Path('data/themes').mkdir(parents=True) subprocess.run(['git', 'init', '-b', 'main'], check=True) set_git_identity() subprocess.run(['git', 'submodule', 'add', '../../carla', 'plugins/CarlaBase/carla'], check=True) create_file('src/core/classes.cpp', 'namespace lmms {\nclass TestClass\n}') create_file('debian/lmms-common.docs', '') create_file('debian/copyright', '') create_file('data/locale/de.ts', '\n' ' \n' ' TestClass\n' ' \n' ' \n' ' About LMMS\n' ' Über LMMS\n' ' \n' '\n' '\n') subprocess.run(['git', 'commit', '-m', 'Initial commit'], check=True) return self def __exit__(self, type, value, traceback): """Leave and destroy temporary test environment""" os.chdir('..') self.lmms_tmpdir.cleanup() def expect(self, expectation: str): """Check if "expectation" is in the output""" if expectation not in self.result.stdout: raise RuntimeError(f'Expected "{expectation}" in script output') def run(self, expected_returncode: int = 1): # default: something goes wrong ("to the safe side") """Run the script, check the exit code and store the result""" self.result = subprocess.run([str(self.scriptpath)], capture_output=True, text=True) print('--->8--- Script output BEGIN --->8---') print(self.result.stdout) print('--->8--- Script output END --->8---') if self.result.stderr: print('--->8--- Script error output BEGIN --->8---') print(self.result.stderr) print('--->8--- Script error output END --->8---') # make sure script returned "error" (because we test for errors) and that the output is as expected if self.result.returncode != expected_returncode: raise RuntimeError(f"Script \"check-strings\" returned {self.result.returncode}, " f"but {expected_returncode} expected") lmms_main_path = Path(__file__).resolve().parent.parent.parent with tempfile.TemporaryDirectory() as tmpdir: os.chdir(tmpdir) check_strings = lmms_main_path / 'tests' / 'scripted' / 'check-strings' check_namespace = lmms_main_path / 'tests' / 'scripted' / 'check-namespace' # create dummy carla repo Path('carla').mkdir() os.chdir('carla') subprocess.run(['git', 'init', '-b', 'main'], check=True) set_git_identity() create_file('README.md', 'hello world') subprocess.run(['git', 'commit', '-m', 'Initial commit'], check=True) os.chdir('..') Path('lmms').mkdir() os.chdir('lmms') # minimal working example with ScriptTest(check_strings) as test: test.run(0) # exitcode 0 - no errors expected test.expect('0 errors') with ScriptTest(check_strings) as test: create_file('data/locale/fr.ts', '\n' ' \n' ' TestClass\n' ' \n' ' \n' ' About LMMS\n' ' À propos de LMMS\n' ' \n' '\n' '\n') test.run() test.expect('Error: data/locale: Source file does not exist: ../../src/core/non-existent.cpp') test.expect('1 errors') with ScriptTest(check_strings) as test: create_file('data/locale/fr.ts', '\n' ' \n' ' NonExistentClass\n' ' \n' ' \n' ' About LMMS\n' ' À propos de LMMS\n' ' \n' '\n' '\n') test.run() test.expect('Error: data/locale: Class does not exist in source code: NonExistentClass') test.expect('1 errors') with ScriptTest(check_strings) as test: create_file('data/themes/classic/style.css', 'lmms--gui--NonExistentClass {' '\tcolor: #d1d8e4;\n' '}') test.run() test.expect('Error: data/themes/classic/style.css: Class does not exist in source code: NonExistentClass') test.expect('1 errors') with ScriptTest(check_strings) as test: create_file('debian/patches/clang.patch', '/plugins/non-existent-file') test.run() test.expect('Error: debian/patches/clang.patch: Source file does not exist: plugins/non-existent-file') test.expect('1 errors') with ScriptTest(check_strings) as test: create_file('debian/lmms-common.docs', '/plugins/caps.html') test.run() test.expect('Error: debian/lmms-common.docs: Path does not exist: /plugins/caps.html') test.expect('1 errors') with ScriptTest(check_strings) as test: create_file('debian/copyright', 'Files: NonExistent') test.run() test.expect('Error: debian/copyright: Glob/Path does not exist: NonExistent') test.expect('1 errors') with ScriptTest(check_namespace) as test: # minimal working example test.run(0) # exitcode 0 - no errors expected test.expect('0 errors') create_file('01_OddBraceWithinMacro.cpp', ''' #if HAS_EVEN_BRACES namespace lmms {} #endif namespace lmms { #if HAS_ODD_BRACE } #endif ''') create_file('02_IncludeInCodeBlock.cpp', ''' #include extern "C" { #include "alright.c" } namespace lmms { #include } ''') create_file('03_MacroComments.h', ''' #ifndef HEADER_GUARD_NEEDS_NO_COMMENT #define HEADER_GUARD_NEEDS_NO_COMMENT #ifdef HAS_NESTED_NEEDS_COMMENT #ifdef SHORT_NO_NESTED_NEEDS_NO_COMMENT namespace lmms {} #endif #endif #ifdef HAS_COMMENT #ifdef SHORT_NO_NESTED_NEEDS_NO_COMMENT #else #endif #endif // HAS_COMMENT #endif ''') create_file('04_NamespaceComments.cpp', ''' namespace lmms { namespace ShortNamespace { class WithDeclarationsOnly; } namespace LongNamespace { class WithDefinition { int x; }; } } // namespace lmms ''') create_file('05_NoHeaderGuard.h', ''' #include namespace lmms {} ''') create_file('06_PragmaButNoLmms.h', ''' // should not cause header guard warning #pragma once namespace not_lmms {} ''') create_file('07_MismatchingEndifName.h', ''' #ifndef ABC_H namespace lmms { \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n } #endif // XYZ_H ''') create_file('08_MismatchingNamespaceEndName.h', ''' #ifndef ABC_H namespace lmms { {} } // namespace smml #endif // ABC_H ''') create_file('09_NoEndifAfterElseIsOk.cpp', ''' #ifdef XYZ namespace lmms { \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n } #else namespace lmms { \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n } #endif ''') test.run() test.expect('8 errors') test.expect(''' Error: 01_OddBraceWithinMacro.cpp:7: Expected #endif before } Error: 02_IncludeInCodeBlock.cpp:7: #include inside a code block Error: 03_MacroComments.h:8: Missing comment // HAS_NESTED_NEEDS_COMMENT Error: 04_NamespaceComments.cpp:10: Missing comment // namespace LongNamespace Error: 05_NoHeaderGuard.h: First statement should be header guard Error: 06_PragmaButNoLmms.h: File has no namespace lmms Error: 07_MismatchingEndifName.h:36: Missing comment // ABC_H Error: 08_MismatchingNamespaceEndName.h:5: Missing comment // namespace lmms ''') # if we made it until here without an exception, all tests have been passed print("SUCCESS")