diff --git a/.travis.yml b/.travis.yml index 4b71685..d318aee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,8 +29,8 @@ deploy: secure: gDAuRNlF2uVhVHyZtJrX6MwNxnkfkQzohrC/6UcKAgqt+NKs4vZyq5FzTUceiDAkB0se70ZVx08e9ibAiXP/b7D1MPkAEiRxt9J6Vu3x6Bi1kPPuK5RfjFeT3gc1SbrULAP8Nz0NdU0chUhei6/V5NGhTegwp925DJOISq7+Ibw= file_glob: true file: - - 'pawnc-*-linux.tar.gz' - - 'pawnc-*-macos.zip' + - 'build/pawnc-*-linux.tar.gz' + - 'build/pawnc-*-macos.zip' draft: true skip_cleanup: true on: diff --git a/CMakeLists.txt b/CMakeLists.txt index b386580..1a2c664 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,11 @@ project(pawn C) cmake_minimum_required(VERSION 2.8) +set(OUTPUT_DIR ${CMAKE_BINARY_DIR}/products) +add_subdirectory(source/compiler ${OUTPUT_DIR}) + # For the compiler we only utilize headers from source/amx, i.e. we don't # actually build any of the AMX modules. -# add_subdirectory(source/amx) - -add_subdirectory(source/compiler) +if(BUILD_AMX) + add_subdirectory(source/amx ${OUTPUT_DIR}) +endif() diff --git a/appveyor.yml b/appveyor.yml index bf09638..b185ace 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,7 +5,8 @@ configuration: - RelWithDebInfo before_build: - - cmake . -G "Visual Studio 14 2015" -DCPACK_GENERATOR=ZIP -DCMAKE_GENERATOR_TOOLSET=v100 + - mkdir build && cd build + - cmake ../source -G "Visual Studio 14 2015" -DCPACK_GENERATOR=ZIP -DCMAKE_GENERATOR_TOOLSET=v100 build_script: - cmake --build . --config %CONFIGURATION% @@ -15,7 +16,7 @@ test_script: - cmake --build . --config %CONFIGURATION% --target pawncc_tests artifacts: - - path: pawnc-*-windows.zip + - path: build/pawnc-*-windows.zip name: Binary package deploy: diff --git a/source/compiler/CMakeLists.txt b/source/compiler/CMakeLists.txt index 51cee48..f7c5c56 100644 --- a/source/compiler/CMakeLists.txt +++ b/source/compiler/CMakeLists.txt @@ -139,6 +139,9 @@ target_link_libraries(pawncc pawnc) # The Pawn disassembler set(PAWNDISASM_SRCS pawndisasm.c + sc.h + ../amx/amx.h + ../amx/amxdbg.h ../amx/amxdbg.c ) add_executable(pawndisasm ${PAWNDISASM_SRCS}) @@ -161,6 +164,15 @@ if(MSVC) endif() # Generate targets for running compiler tests +set(PAWNRUNS_SRCS + pawnruns.c + ../amx/amx.c + ../amx/amx.h + ../amx/amxaux.c + ../amx/amxaux.h + ../amx/amxcons.c + ../amx/amxcore.c) +add_executable(pawnruns ${PAWNRUNS_SRCS}) add_subdirectory(tests) # Generate a binary package with CPack diff --git a/source/compiler/pawnruns.c b/source/compiler/pawnruns.c new file mode 100644 index 0000000..cc955fe --- /dev/null +++ b/source/compiler/pawnruns.c @@ -0,0 +1,55 @@ +/* A simpler runner based on source/amx/pawnrun/prun1.c. + * + * Copyright (c) ITB CompuPhase, 2001-2005 + * + * This file may be freely used. No warranties of any kind. + */ + +#include +#include /* for exit() */ +#include +#include /* for memset() (on some compilers) */ +#include "../amx/amx.h" +#include "../amx/amxaux.h" + +static void ErrorExit(AMX *amx, int errorcode) +{ + printf("Run time error %d: \"%s\"\n", errorcode, aux_StrError(errorcode)); + exit(1); +} + +static void PrintUsage(char *program) +{ + printf("Usage: %s \n is a compiled script.\n", program); + exit(1); +} + +int main(int argc,char *argv[]) +{ + extern AMX_NATIVE_INFO console_Natives[]; + extern AMX_NATIVE_INFO core_Natives[]; + + AMX amx; + cell ret = 0; + int err; + + if (argc != 2) + PrintUsage(argv[0]); + + err = aux_LoadProgram(&amx, argv[1], NULL); + if (err != AMX_ERR_NONE) + ErrorExit(&amx, err); + + amx_Register(&amx, console_Natives, -1); + err = amx_Register(&amx, core_Natives, -1); + if (err != AMX_ERR_NONE) + ErrorExit(&amx, err); + + err = amx_Exec(&amx, &ret, AMX_EXEC_MAIN); + if (err != AMX_ERR_NONE) + ErrorExit(&amx, err); + printf("%s returns %ld\n", argv[1], (long)ret); + + aux_FreeProgram(&amx); + return 0; +} diff --git a/source/compiler/tests/CMakeLists.txt b/source/compiler/tests/CMakeLists.txt index 26d4d97..1921ab6 100644 --- a/source/compiler/tests/CMakeLists.txt +++ b/source/compiler/tests/CMakeLists.txt @@ -1,9 +1,14 @@ -find_package(PythonInterp 2.7 REQUIRED) +find_package(PythonInterp 2.7) -add_custom_target(pawncc_tests COMMAND - COMMAND ${PYTHON_EXECUTABLE} - ${CMAKE_CURRENT_SOURCE_DIR}/run_tests.py - -c $ - -i ../../../include - DEPENDS pawncc - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +if(PYTHONINTERP_FOUND) + add_custom_target(pawncc_tests COMMAND + COMMAND ${PYTHON_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/run_tests.py + -c $ + -r $ + -i ../../../include + DEPENDS pawncc + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +else() + message("Python was not found, you will not be able to run the compiler tests") +endif() diff --git a/source/compiler/tests/md_array_crash_gh_220.meta b/source/compiler/tests/md_array_crash_gh_220.meta index 69deb64..065c642 100644 --- a/source/compiler/tests/md_array_crash_gh_220.meta +++ b/source/compiler/tests/md_array_crash_gh_220.meta @@ -2,6 +2,7 @@ 'test_type': 'output_check', 'errors': """ md_array_crash_gh_220.pwn(6) : fatal error 111: user error: OK + Compilation aborted. """ } diff --git a/source/compiler/tests/run_tests.py b/source/compiler/tests/run_tests.py index 2280c74..946bbce 100644 --- a/source/compiler/tests/run_tests.py +++ b/source/compiler/tests/run_tests.py @@ -10,11 +10,14 @@ import sys parser = argparse.ArgumentParser() parser.add_argument('-c', '--compiler', required=True, - help='path to the pawncc executable') + help='path to compiler executable (pawncc or pawncc.exe)') parser.add_argument('-i', '--include', dest='include_dirs', action='append', - help='add specified directory to include path') + help='add custom include directories for compile tests') +parser.add_argument('-r', '--runner', + required=True, + help='path to runner executable (pawnrun or pawnrun.exe)') options = parser.parse_args(sys.argv[1:]) def run_compiler(args): @@ -30,11 +33,7 @@ def run_compiler(args): stderr=subprocess.PIPE) class OutputCheckTest: - def __init__(self, - name, - source_file, - errors=None, - extra_args=None): + def __init__(self, name, source_file, errors=None, extra_args=None): self.name = name self.source_file = source_file self.errors = errors @@ -54,19 +53,14 @@ class OutputCheckTest: No errors specified and process exited with non-zero status """ else: - errors = stderr.decode('utf-8').splitlines() - errors = [e.strip() for e in errors if e.strip()] - expected_errors = self.errors.splitlines() - expected_errors = [e.strip() for e in expected_errors if e.strip()] + errors = stderr.decode('utf-8').strip(' \t\r\n').replace('\r', '') + expected_errors = self.errors.strip(' \t\r\n').replace('\r', '') if errors != expected_errors: result = False self.fail_reason = ( 'Error output didn\'t match\n\nExpected errors:\n\n{}\n\n' 'Actual errors:\n\n{}' - ).format( - '\n'.join(expected_errors).strip(' \t\r\n'), - '\n'.join(errors).strip(' \t\r\n') - ) + ).format(expected_errors, errors) return result class CrashTest: @@ -79,10 +73,37 @@ class CrashTest: # TODO: Check if the process crashed. return True -test_types = { - 'output_check': OutputCheckTest, - 'crash': CrashTest -} +class RuntimeTest: + def __init__(self, name, amx_file, output, should_fail): + self.name = name + self.amx_file = amx_file + self.output = output + self.should_fail = should_fail + + def run(self): + process = subprocess.Popen([options.runner, self.amx_file], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = process.communicate() + output = '' + if stdout is not None: + output = stdout.decode('utf-8') + if stderr is not None: + output += stderr.decode('utf-8') + output = output.strip(' \t\r\n').replace('\r', '') + expected_output = self.output.strip(' \t\r\n').replace('\r', '') + if not self.should_fail and process.returncode != 0: + self.fail_reason = ( + 'Runner exited with status {}\n\nOutput: {}' + ).format(process.returncode, output) + return False + if output != expected_output: + self.fail_reason = ( + 'Output didn\'t match\n\nExpected output:\n\n{}\n\n' + 'Actual output:\n\n{}' + ).format(expected_output, output) + return False + return True tests = [] num_tests_disabled = 0 @@ -100,6 +121,11 @@ for meta_file in glob.glob('*.meta'): extra_args=metadata.get('extra_args'))) elif test_type == 'crash': tests.append(CrashTest(name=name, source_file=name + '.pwn')) + elif test_type == 'runtime': + tests.append(RuntimeTest(name=name, + amx_file=name + '.amx', + output=metadata.get('output'), + should_fail=metadata.get('should_fail'))) else: raise KeyError('Unknown test type: ' + test_type) diff --git a/source/compiler/tests/runtime_test_example.meta b/source/compiler/tests/runtime_test_example.meta new file mode 100644 index 0000000..49f67ed --- /dev/null +++ b/source/compiler/tests/runtime_test_example.meta @@ -0,0 +1,7 @@ +{ + 'test_type': 'runtime', + 'output': """ + Run time error 4: "Array index out of bounds" + """, + 'should_fail': True +} diff --git a/source/compiler/tests/runtime_test_example.pwn b/source/compiler/tests/runtime_test_example.pwn new file mode 100644 index 0000000..9907096 --- /dev/null +++ b/source/compiler/tests/runtime_test_example.pwn @@ -0,0 +1,10 @@ +#include + +main() { + new a[1] = {1}; + new i = 1; + + // When compiled with at least debug level 1 (default) the line below should produce: + // Run time error 4: "Array index out of bounds" + printf("%d", a[i]); +} diff --git a/source/compiler/tests/too_many_args_crash_gh_298.meta b/source/compiler/tests/too_many_args_crash_gh_298.meta index 131078f..9017075 100644 --- a/source/compiler/tests/too_many_args_crash_gh_298.meta +++ b/source/compiler/tests/too_many_args_crash_gh_298.meta @@ -6,6 +6,7 @@ too_many_args_crash_gh_298.pwn(2) : warning 215: expression has no effect too_many_args_crash_gh_298.pwn(2) : error 001: expected token: ";", but found ")" too_many_args_crash_gh_298.pwn(2) : error 029: invalid expression, assumed zero too_many_args_crash_gh_298.pwn(2) : fatal error 107: too many error messages on one line + Compilation aborted. """ }