def test_build(self): """ Initialize a new project and try to build it """ project = stm32pio.core.project.Stm32pio( STAGE_PATH, parameters={'project': { 'board': PROJECT_BOARD }}) project.generate_code() project.pio_init() project.patch() self.assertEqual(project.build(), 0, msg="Build failed")
def test_regenerate_code(self): """ Simulate a new project creation, its changing and CubeMX code re-generation (for example, after adding new hardware features and some new files by a user) """ project = stm32pio.core.project.Stm32pio( STAGE_PATH, parameters={'project': { 'board': PROJECT_BOARD }}) # Generate a new project ... project.generate_code() project.pio_init() project.patch() # ... change it: test_file_1 = STAGE_PATH.joinpath('Src', 'main.c') test_content_1 = "*** TEST STRING 1 ***\n" test_file_2 = STAGE_PATH.joinpath('Inc', 'my_header.h') test_content_2 = "*** TEST STRING 2 ***\n" # - add some sample string inside CubeMX' /* BEGIN - END */ block main_c_content = test_file_1.read_text() pos = main_c_content.index("while (1)") main_c_new_content = main_c_content[: pos] + test_content_1 + main_c_content[ pos:] test_file_1.write_text(main_c_new_content) # - add new file inside the project test_file_2.write_text(test_content_2) # Re-generate CubeMX project project.generate_code() # Check if added information has been preserved for test_content, after_regenerate_content in [ (test_content_1, test_file_1.read_text()), (test_content_2, test_file_2.read_text()) ]: with self.subTest( msg= f"User content hasn't been preserved in {after_regenerate_content}" ): self.assertIn(test_content, after_regenerate_content)
def test_build_should_handle_error(self): """ Build an empty project so PlatformIO should return an error """ project = stm32pio.core.project.Stm32pio( STAGE_PATH, parameters={'project': { 'board': PROJECT_BOARD }}) project.pio_init() with self.assertLogs(level='ERROR') as logs: self.assertNotEqual(project.build(), 0, msg="Build error was not indicated") # next() - Technique to find something in array, string, etc. (or to indicate that there is no of such) self.assertTrue(next( (True for item in logs.output if "PlatformIO build error" in item), False), msg="Error message does not match")
def test_pio_init(self): """ Consider that the existence of a 'platformio.ini' file showing a successful PlatformIO project initialization. There are other artifacts that can be checked too but we are interested only in a 'platformio.ini' anyway. Also, check that it is a correct configparser.ConfigParser file and is not empty """ project = stm32pio.core.project.Stm32pio( STAGE_PATH, parameters={'project': { 'board': PROJECT_BOARD }}) result = project.pio_init() self.assertEqual(result, 0, msg="Non-zero return code") self.assertTrue(STAGE_PATH.joinpath('platformio.ini').is_file(), msg="platformio.ini is not there") platformio_ini = configparser.ConfigParser(interpolation=None) self.assertGreater(len( platformio_ini.read(STAGE_PATH.joinpath('platformio.ini'))), 0, msg='platformio.ini is empty')
logger = logging.getLogger('stm32pio') # you can also provide a logger to the project instance itself logger.setLevel(logging.INFO) # use logging.DEBUG for the verbose output handler = logging.StreamHandler() # default STDERR stream handler.setFormatter(logging.Formatter('%(levelname)s %(message)s')) # some pretty simple output format logger.addHandler(handler) # Or you can just use a built-in logging schema which is basically doing the same stuff for you. Note though, that only # a single option should be either picked at a time, otherwise records duplication will occur import stm32pio.cli.app # logger = stm32pio.cli.app.setup_logging() # comment section above and uncomment me # There are multiple ways of logging configuration, it as an essential feature opening doors to many possible library # applications # Let's try again project.pio_init() # now there should be some handful logging records! # INFO starting PlatformIO project initialization... # INFO successful PlatformIO project initialization # Finally, you can use the high-level API - same as for the CLI version of the application - to perform complex tasks project.clean() # clean up the previous results first return_code = stm32pio.cli.app.main( sys_argv=['new', '--directory', './stm32pio-test-project', '--board', 'nucleo_f031k6'], should_setup_logging=False) # again, disabling the default logging to prevent an interference print(return_code) # main() is designed to never throw any exception so we monitor the response via the return code # 0 project.clean() # clean up after yourself # In the end, let's check our environment - programs used by the stm32pio. This can be done through the 'validate' # command. Like for the state, its output is ready for printing right away: print(project.validate_environment())
def main(sys_argv: List[str] = None, should_setup_logging: bool = True) -> int: """ Can be used as a high-level wrapper to perform independent tasks. Example: ret_code = stm32pio.app.main(sys_argv=['new', '-d', '~/path/to/project', '-b', 'nucleo_f031k6', '--with-build']) Args: sys_argv: list of strings CLI arguments should_setup_logging: if this is true, the preferable default logging schema would be applied, otherwise it is a caller responsibility to provide (or do not) some logging configuration. The latter can be useful when the outer code makes sequential calls to this API so it is unwanted to append the logging handlers every time (e.g. when unit-testing) Returns: 0 on success, -1 otherwise """ if sys_argv is None: sys_argv = sys.argv[1:] args = parse_args(sys_argv) if args is not None and args.subcommand == 'gui': gui_args = [arg for arg in sys_argv if arg != 'gui'] import stm32pio.gui.app as gui_app return gui_app.main(sys_argv=gui_args).exec_() elif args is not None and args.subcommand is not None: logger = setup_logging(verbose=args.verbose, dummy=not should_setup_logging) else: print("\nNo arguments were given, exiting...") return 0 project = None # Main routine try: if args.subcommand == 'init': project = stm32pio.core.project.Stm32pio( args.path, parameters={'project': { 'board': args.board }}, instance_options={'save_on_destruction': True}) if args.store_content: project.config.save_content_as_ignore_list() if project.config.get('project', 'board') == '': logger.warning( "PlatformIO board identifier is not specified, it will be needed on PlatformIO project " "creation. Type 'pio boards' or go to https://platformio.org to find an appropriate " "identifier") logger.info( f"project has been initialized. You can now edit {stm32pio.core.settings.config_file_name} " "config file") if args.editor: project.start_editor(args.editor) elif args.subcommand == 'generate': project = stm32pio.core.project.Stm32pio(args.path) project.generate_code() if args.with_build: project.build() if args.editor: project.start_editor(args.editor) elif args.subcommand == 'pio_init': project = stm32pio.core.project.Stm32pio( args.path, parameters={'project': { 'board': args.board }}, instance_options={'save_on_destruction': True}) project.pio_init() elif args.subcommand == 'patch': project = stm32pio.core.project.Stm32pio(args.path) project.patch() elif args.subcommand == 'new': project = stm32pio.core.project.Stm32pio( args.path, parameters={'project': { 'board': args.board }}, instance_options={'save_on_destruction': True}) if args.store_content: project.config.save_content_as_ignore_list() if project.config.get('project', 'board') == '': logger.info( f"project has been initialized. You can now edit {stm32pio.core.settings.config_file_name} " "config file") raise Exception( "PlatformIO board identifier is not specified, it is needed for PlatformIO project " "creation. Type 'pio boards' or go to https://platformio.org to find an appropriate " "identifier") project.generate_code() project.pio_init() project.patch() if args.with_build: project.build() if args.editor: project.start_editor(args.editor) elif args.subcommand == 'status': project = stm32pio.core.project.Stm32pio(args.path) print(project.state) elif args.subcommand == 'validate': project = stm32pio.core.project.Stm32pio(args.path) print(project.validate_environment()) elif args.subcommand == 'clean': project = stm32pio.core.project.Stm32pio(args.path) if args.store_content: project.config.save_content_as_ignore_list() else: project.clean(quiet_on_cli=args.quiet) # Global errors catching. Core library is designed to throw the exception in cases when there is no sense to # proceed. Of course this also suppose to handle any unexpected behavior, too except Exception: stm32pio.core.logging.log_current_exception( logger, config=project.config if (project is not None and hasattr(project, 'config')) else None) return -1 return 0
def main(sys_argv: List[str] = None, should_setup_logging: bool = True) -> int: """ Entry point to the CLI edition of application. Since this is a highest-order wrapper, it can be used to programmatically the application (for testing, embedding, etc.). Example: ret_code = stm32pio.app.main(sys_argv=['new', '-d', '~/path/to/project', '-b', 'nucleo_f031k6', '--with-build']) :param sys_argv: list of CLI arguments :param should_setup_logging: if True, a reasonable default logging schema would be applied, otherwise it is on caller to resolve (or not) some logging configuration. The latter can be useful when an outer code makes sequential calls to this API so it is unwanted to append logging handlers every time (e.g. when unit-testing) :return: 0 on success, -1 otherwise """ if sys_argv is None: sys_argv = sys.argv[1:] args = parse_args(sys_argv) if args is not None and args.command == 'gui': gui_args = [arg for arg in sys_argv if arg != 'gui'] import stm32pio.gui.app as gui app = gui.create_app(sys_argv=gui_args) return app.exec_() elif args is not None and args.command is not None: logger = setup_logging(verbose=args.verbose, dummy=not should_setup_logging) else: print("\nNo arguments were given, exiting...") return 0 project = None # Wrap the main routine into try...except to gently handle possible error (API is designed to throw in certain # situations when it doesn't make much sense to continue with the met conditions) try: if args.command == 'init': project = stm32pio.core.project.Stm32pio( args.path, parameters={'project': { 'board': args.board }}, save_on_destruction=True) if args.store_content: project.config.set_content_as_ignore_list() if project.config.get('project', 'board') == '': logger.warning(no_board_message) project.inspect_ioc_config() logger.info(init_message) if args.editor: project.start_editor(args.editor) elif args.command == 'generate': project = stm32pio.core.project.Stm32pio(args.path) if project.config.get('project', 'inspect_ioc', fallback='0').lower( ) in stm32pio.core.settings.yes_options: project.inspect_ioc_config() project.generate_code() if args.with_build: project.build() if args.editor: project.start_editor(args.editor) elif args.command == 'pio_init': project = stm32pio.core.project.Stm32pio( args.path, parameters={'project': { 'board': args.board }}, save_on_destruction=True) if project.config.get('project', 'inspect_ioc', fallback='0').lower( ) in stm32pio.core.settings.yes_options: project.inspect_ioc_config() project.pio_init() elif args.command == 'patch': project = stm32pio.core.project.Stm32pio(args.path) if project.config.get('project', 'inspect_ioc', fallback='0').lower( ) in stm32pio.core.settings.yes_options: project.inspect_ioc_config() project.patch() elif args.command == 'new': project = stm32pio.core.project.Stm32pio( args.path, parameters={'project': { 'board': args.board }}, save_on_destruction=True) if args.store_content: project.config.set_content_as_ignore_list() if project.config.get('project', 'board') == '': logger.info(init_message) raise Exception(no_board_message) if project.config.get('project', 'inspect_ioc', fallback='0').lower( ) in stm32pio.core.settings.yes_options: project.inspect_ioc_config() project.generate_code() project.pio_init() project.patch() if args.with_build: project.build() if args.editor: project.start_editor(args.editor) elif args.command == 'status': project = stm32pio.core.project.Stm32pio(args.path) print(project.state) elif args.command == 'validate': project = stm32pio.core.project.Stm32pio(args.path) print(project.validate_environment()) elif args.command == 'clean': project = stm32pio.core.project.Stm32pio(args.path) if args.store_content: project.config.set_content_as_ignore_list() project.config.save() else: project.clean(quiet=args.quiet) except (Exception, ): stm32pio.core.log.log_current_exception( logger, config=project.config if project is not None else None) return -1 return 0