From 6e9ca998a8499c720bba7e0dda00df2ba0ecc664 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Thu, 1 Feb 2018 18:07:14 +0100 Subject: [PATCH 01/65] initial commit slowing building up a base with tests, to ensure functionality. add a function? add a/multiple test. --- .gitignore | 4 + README.md | 29 + .../simulation_objecs/simulation_objects.py | 558 ++++++++++++++++++ .../simulation_objects_tests.py | 130 ++++ requirements.txt | 2 + setup.py | 18 + 6 files changed, 741 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 bloodytools/simulation_objecs/simulation_objects.py create mode 100644 bloodytools/simulation_objecs/simulation_objects_tests.py create mode 100644 requirements.txt create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed9e95b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +env35_pyqt +build-versuch_zwei-Desktop_Qt_5_8_0_MSVC2015_64bit-Debug +/env +*.pyc diff --git a/README.md b/README.md new file mode 100644 index 0000000..4d26513 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +Bloody( tools ) +=========== + +> Automatation tool for several different aspects of all dps and some tank specs in World of Warcraft using SimulationCraft. + +## Requirements +You need the latest [SimulationCraft](http://downloads.simulationcraft.org/?C=M;O=D) version ([GitHub Repository](https://github.com/simulationcraft/simc)), [Python 3.5](https://www.python.org/downloads/) or newer and the module [simc_support](https://github.com/Bloodmallet/simc_support), which is handled in the requirements.txt. + +## Download +Download or clone this repository into the SimulationCraft directory. `simulationcraft\bloodytools` + +## Setup +Start python environement. Install dependencies. +```sh +$ \Scripts\active +()$ pip install -U -r .\requirements.txt +``` + +`pip freeze` should return something like this: "-e git+https://github.com/Bloodmallet/simc_support.git@e806d5ca289072684c6aca5fb03ce2b44e88cc4e#egg=simc_support" + +## Getting started +Edit settings.py to your liking using any text editor. Start python environement. Start bloodytools. +```sh +$ \Scripts\active +()$ python .\bloodytools.py +``` + +## Development +If you see a lack of features somewhere or ways to improve the quality of the code, please contact me or create an [issue](https://github.com/Bloodmallet/bloodytools/issues). diff --git a/bloodytools/simulation_objecs/simulation_objects.py b/bloodytools/simulation_objecs/simulation_objects.py new file mode 100644 index 0000000..8c7f56e --- /dev/null +++ b/bloodytools/simulation_objecs/simulation_objects.py @@ -0,0 +1,558 @@ +# date data +import datetime +# wow game data and simc input checks +from simc_support import simc_checks as simc_checks +# sys operations and data +import sys +import uuid +import logging + +logger = logging.getLogger(__name__) + +class Error( Exception ): + """Base class for exceptions in this module""" + pass + + +class AlreadySetError( Error ): + """ + Exception raised if a value already exists. + + Attributes: + value -- value which was tried to set + message -- explanation of the error + """ + def __init__( self, value, message ): + self.value = value + self.message = message + logger.exception(self.message) + + +class InputError( Error ): + """ + Exception raised for errors in the input. + + Attributes: + expression -- input expression in which the error occurred + message -- explanation of the error + """ + def __init__( self, expression, message ): + self.expression = expression + self.message = message + logger.exception(self.message) + + +class NotSetYetError( Error ): + """ + Exception raised when simulations aren't done yet. + + Attributes: + value -- value which was tried to get + message -- explanation of the error + """ + def __init__( self, value, message ): + self.value = value + self.message = message + logger.exception(self.message) + + +class StillInProgressError( Error ): + """ + Exception raised when simulation is still in progress. + + Attributes: + value -- value which was tried to get + message -- explanation of the error + """ + def __init__( self, value, message ): + self.value = value + self.message = message + logger.exception(self.message) + + +class SimulationError(object): + """ + Exception raised when simulations aren't done yet. + + Attributes: + value -- value which was tried to get + message -- explanation of the error + """ + def __init__(self, simulation_output): + super(SimulationError, self).__init__() + self.simulation_output = simulation_output + + +class simulation_data(): + """ + Manages all META-information for a simulation and the result. + """ + def __init__( self, + calculate_scale_factors="0", + default_actions="1", + default_skill="1.0", + executionable=None, + fight_style="patchwerk", + fixed_time="1", + html="", + iterations="250000", + log="0", + name=None, + optimize_expressions="1", + ptr="0", + ready_trigger="1", + simc_arguments=[], + target_error="0.1", + threads="" ): + + super( simulation_data, self ).__init__() + + # simc setting to calculate scale factors (stat weights) + self.calculate_scale_factors = calculate_scale_factors + + # simc setting to manage default apl usage + logger.debug("Initialising default_actions.") + self.default_actions = default_actions + + # simc setting to manage the accuracy of used apl lines (leave it at 1.0) + logger.debug("Initialising default_skill.") + try: + self.default_skill = str( float( default_skill ) ) + except Exception as e: + logger.error( "{} -- Using default value instead.".format( e ) ) + self.default_skill = "1.0" + + # describes the location and type of the simc executable + logger.debug("Initialising executionable.") + # if no value was set, determine a standard value + if executionable == None: + if sys.platform == 'win32': + logger.debug("Setting Windows default value for executionable.") + self.executionable = "../simc.exe" + else: + logger.debug("Setting Linux default value for executionable.") + self.executionable = "../simc" + else: + try: + self.executionable = str( executionable ) + except Exception as e: + logger.error( e ) + raise e + + # simc setting to determine the fight style + logger.debug("Initialising fight_style.") + if fight_style == "custom" or simc_checks.is_fight_style( fight_style ): + self.fight_style = fight_style + else: + logger.warning( "{} -- Using default value instead.".format( fight_style ) ) + self.fight_style = "patchwerk" + + # simc setting to enable/diable the fixed fight length + self.fixed_time = fixed_time + # simc setting to enable html output + self.html = html + # simc setting to determine the maximum number of run iterations + # (target_error and iterations determine the actually simulated + # iterations count) + self.iterations = str( int( iterations ) ) + # simc setting to enable/disable a log file + self.log = log + # optional name for the data + if not name: + self.name = uuid.uuid4() + else: + self.name = name + # simc setting to enable/disable optimize expressions + self.optimize_expressions = optimize_expressions + # simc setting to enable/disable ptr data + self.ptr = ptr + # simc setting to enable/disable ready_trigger + self.ready_trigger = ready_trigger + # specific data to be run, like talent combinations, specific gear or + # traits + if type( simc_arguments ) == list or simc_arguments == []: + self.simc_arguments = simc_arguments + else: + self.simc_arguments = [simc_arguments] + # simc setting to determine the target_error + self.target_error = target_error + # simc setting to determine the number of used threads, empty string uses + # all available + self.threads = threads + + # creation time of the simulation object + self.so_creation_time = datetime.datetime.utcnow() + # simulation dps result + self.dps = None + # flag to know whether data was generated with external simulation function + self.external_simulation = False + # simulation full report (command line print out) + self.full_report = None + # simulation end time + self.so_simulation_end_time = None + # simulation start time + self.so_simulation_start_time = None + + + def is_equal(self, simulation_data): + """ + @brief Determines if the current and given simulation_data share the + same base. The following attributes are considered base: + calculate_scale_factors, default_actions, default_skill, + executionable, fight_style, fixed_time, html, iterations, log, + optimize_expressions, ptr, ready_trigger, target_error, threads + + @param self The object + @param simulation_data The other simulation data + + @return True if equal base values, False otherwise. + """ + if self.calculate_scale_factors != simulation_data.calculate_scale_factors: + return False + if self.default_actions != simulation_data.default_actions: + return False + if self.default_skill != simulation_data.default_skill: + return False + if self.executionable != simulation_data.executionable: + return False + if self.fight_style != simulation_data.fight_style: + return False + if self.fixed_time != simulation_data.fixed_time: + return False + if self.html != simulation_data.html: + return False + if self.iterations != simulation_data.iterations: + return False + if self.log != simulation_data.log: + return False + if self.optimize_expressions != simulation_data.optimize_expressions: + return False + if self.ptr != simulation_data.ptr: + return False + if self.ready_trigger != simulation_data.ready_trigger: + return False + if self.target_error != simulation_data.target_error: + return False + if self.threads != simulation_data.threads: + return False + return True + + + def get_dps( self ): + """ + @brief Get the dps of the simulation. + + @param self The object + + @return The dps. + """ + if self.dps: + return int( self.dps ) + if self.so_simulation_start_time < datetime.datetime.utcnow() and not self.so_simulation_end_time: + raise StillInProgressError( "dps", "Dps simulation is still in progress." ) + else: + raise NotSetYetError( "dps", "No dps was saved yet." ) + + + def set_dps( self, dps, external=True ): + """ + @brief Sets the dps. + + @param self The object + @param dps The dps + @param external Set to True if dps was calculated using an external + logic + + @return True if dps was set. + """ + # set external_simulation flag, defaults to True, so external simulations + # don't need to pay attention to this + try: + self.external_simulation = bool( external ) + except Exception as e: + raise e + + # raise AlreadySetError if one tries to overwrite previously set data + if self.dps: + raise AlreadySetError( "dps", + "A value for dps was already set to {}.".format( self.get_dps() ) ) + + try: + # slightly more robust than simply cast to int, due to possible float + # numbers as strings + self.dps = int( float( dps ) ) + except Exception as e: + raise e + + return True + + + def get_avg( self, simulation_data ): + """ + @brief Gets the average of the current the given simulation_data. + + @param self The object + @param simulation_data The simulation data + + @return The average as int. + """ + return int( ( self.get_dps() + simulation_data.get_dps() ) / 2 ) + + + def get_simulation_duation( self ): + """ + @brief Return the time, the simulation took. Will raise NotDoneYetError + if simulation didn't start or end yet. + + @param self The object + + @return The simulation duation. + """ + if not self.so_simulation_start_time: + raise NotSetYetError( "so_simulation_start_time", + "Simulation didn't start yet. No simulation duration possible." ) + if not self.so_simulation_end_time: + raise NotSetYetError( "so_simulation_end_time", + "Simulation isn't done yet. No simulation duration possible." ) + + return self.so_simulation_end_time - self.so_simulation_start_time + + + def set_full_report( self, report ): + """ + @brief Saves the report (simulation output). + + @param self The object + @param report The report + + @return True, if saved. + """ + if type( report ) == str: + self.full_report = report + return True + else: + raise TypeError("Report as type {} found but string expected.".format( type( report ) ) ) + + + def set_simulation_end_time( self ): + """ + @brief Set so_simulation_end_time. Raises AlreadySetError if + so_simulation_end_time was already set. + + @param self The object + + @return True, if set. + """ + if not self.so_simulation_end_time: + try: + self.so_simulation_end_time = datetime.datetime.utcnow() + except Exception as e: + raise e + else: + return True + else: + raise AlreadySetError( "so_simulation_end_time", + "End time was already set. Setting it twice is not allowed." ) + + + def set_simulation_start_time( self ): + """ + @brief Set so_simulation_start_time. Can happen multiple times if + simulation failed. + + @param self The object + + @return True, if set. + """ + try: + self.so_simulation_start_time = datetime.datetime.utcnow() + except Exception as e: + raise e + else: + return True + + + def simulate( self ): + """ + @brief Simulates the data using SimulationCraft. Resulting dps are + saved. + + @param self The object + + @return DPS of the simulation. + """ + argument = [ self.executionable ] + argument.append( "iterations=" + self.iterations ) + argument.append( "target_error=" + self.target_error ) + argument.append( "fight_style=" + self.fight_style ) + argument.append( "fixed_time=" + self.fixed_time ) + argument.append( "optimize_expressions=" + self.optimize_expressions ) + argument.append( "default_actions=" + self.default_actions ) + argument.append( "log=" + self.log ) + argument.append( "default_skill="+ self.default_skill ) + argument.append( "ptr=" + self.ptr ) + argument.append( "threads=" + self.threads ) + argument.append( "ready_trigger="+ self.ready_trigger ) + + for simc_argument in simc_arguments: + argument.append( simc_argument ) + + # should prevent additional empty windows popping up...on win32 systems without breaking different OS + if sys.platform == 'win32': + # call simulationcraft in the background. grab output for processing and get dps value + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + + simulation_output = subprocess.run( + argument, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + startupinfo=startupinfo + ) + + + fail_counter = 0 + while simulation_output.returncode != 0 and fail_counter < 5: + logger.error( "ERROR: An Error occured during simulation." ) + logger.error( "args: " + str( simulation_output.args ) ) + logger.error( "stdout: " + str( simulation_output.stdout ) ) + simulation_output = subprocess.run( + argument, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + startupinfo=startupinfo + ) + fail_counter += 1 + + else: + simulation_output = subprocess.run( + argument, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True + ) + + + fail_counter = 0 + while simulation_output.returncode != 0 and fail_counter < 5: + logger.error( "ERROR: An Error occured during simulation." ) + logger.error( "args: " + str( simulation_output.args ) ) + logger.error( "stdout: " + str( simulation_output.stdout ) ) + simulation_output = subprocess.run( + argument, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True + ) + fail_counter += 1 + + if fail_counter >= 5: + self.error = simulation_output.stdout + raise SimulationError( self.error ) + + # set simulation end time + self.set_simulation_end_time() + # save output + self.set_full_report( simulation_output ) + + actor_flag = True + run_dps = "DPS: 0.0" + for line in simulation_output.stdout.splitlines(): + # needs this check to prevent grabbing the boss dps + if "DPS:" in line and actor_flag: + run_dps = line + actor_flag = False + + dps_value = run_dps.split()[ 1 ] + + # save dps value + self.set_dps( dps_value, external=False ) + + return self.get_dps() + + +class simulation_group(): + """ + @brief simulator_group holds one or multiple simulation_data as profiles + and can simulate them either serialized or parallel. Parallel uses + SimulationCrafts own profilesets feature. Dps values are saved in + the simulation_data. + """ + def __init__( self, simulation_data, name="" ): + super( simulation_group, self ).__init__() + if type( simulation_data ) == list: + self.profiles = simulation_data + elif type( simulation_data ) == simulation_data: + self.profiles = [ simulation_data ] + else: + logger.error( "Wrong type {} of simulation_data (need list of or single simulation_data).".format( type( simulation_data ) ) ) + raise TypeError( "simulation_group initiation", + "Wrong type " + str( type( simulation_data ) ) + " of simulation_data (need list/single simulation_data)." ) + # optional name of the simulation_group + self.name = name + + if not selfcheck(): + raise InputError( "simulation_group", + "simulation_data base data wasn't coherent.") + + + def selfcheck( self ): + """ + @brief Compares the base content of all profiles. All profiles need to + have the same values in each standard field (__init__ of + simulation_data). + + @param self The object + + @return True if all data is coherent, False otherwise. + """ + i = 0 + while i + 1 < len( self.profiles ): + if not self.profiles[ i ].is_equal( self.profiles[ i + 1 ] ): + return False + i += 1 + return True + + + def simulate( self ): + """ + @brief Triggers the simulation of all profiles. + + @param self The object + + @return True on success. + """ + if length( self.profiles ) == 1: + # if only one profiles is in the group this profile is simulated normally + self.profiles[ 0 ].simulate() + else: + # profile set creation based on profiles content + # create file with profile set content + # start simulation with profilesets + # grab data + # push data into simulation_data.set_dps(dps) + pass + + + + def add( self, simulation_data ): + """ + @brief Add another simulation_data object to the group + + @param self The object + @param simulation_data The simulation information + + @return True, if adding data was successfull. Exception otherwise. + """ + if type( simulation_data ) == simulation_data: + try: + self.profiles.append( simulation_data ) + except Exception as e: + raise e + else: + return True + else: + raise TypeError( "simulation_group.add", + "Wrong type of simulation_data (need simulation_data)." ) diff --git a/bloodytools/simulation_objecs/simulation_objects_tests.py b/bloodytools/simulation_objecs/simulation_objects_tests.py new file mode 100644 index 0000000..4cad956 --- /dev/null +++ b/bloodytools/simulation_objecs/simulation_objects_tests.py @@ -0,0 +1,130 @@ +import datetime +import logging +import unittest +import uuid + +import simulation_objects + +logger = logging.getLogger(__name__) + +## +## @brief Simple tests for the handling of init values for objects of the +## simulation_data class +## +class TestSimulationDataInit(unittest.TestCase): + + ## + ## @brief Cleans up after each test. + ## + ## @param self The object + ## + ## @return Nothing + ## + def tearDown(self): + self.simulation_data = None + + ## + ## @brief Test all available and left empty input values for their + ## correct default values. + ## + ## @param self The object + ## + ## @return { description_of_the_return_value } + ## + def test_empty(self): + self.simulation_data = simulation_objects.simulation_data() + self.assertEqual( self.simulation_data.calculate_scale_factors, "0" ) + self.assertEqual( self.simulation_data.default_actions, "1" ) + self.assertEqual( self.simulation_data.default_skill, "1.0" ) + self.assertEqual( self.simulation_data.fight_style, "patchwerk" ) + self.assertEqual( self.simulation_data.fixed_time, "1" ) + self.assertEqual( self.simulation_data.html, "" ) + self.assertEqual( self.simulation_data.iterations, "250000" ) + self.assertEqual( self.simulation_data.log, "0" ) + self.assertNotEqual( self.simulation_data.name, None ) + self.assertEqual( self.simulation_data.optimize_expressions, "1" ) + self.assertEqual( self.simulation_data.ptr, "0" ) + self.assertEqual( self.simulation_data.ready_trigger, "1" ) + self.assertEqual( self.simulation_data.simc_arguments, [] ) + self.assertEqual( self.simulation_data.target_error, "0.1" ) + self.assertEqual( self.simulation_data.threads, "" ) + self.assertEqual( type( self.simulation_data.so_creation_time), datetime.datetime ) + self.assertEqual( self.simulation_data.external_simulation, False ) + self.assertEqual( self.simulation_data.full_report, None ) + self.assertEqual( self.simulation_data.so_simulation_end_time, None ) + self.assertEqual( self.simulation_data.so_simulation_start_time, None ) + + + + ## + ## @brief Test input checks of calculate_scale_factor. + ## + ## @param self The object + ## + ## @return { description_of_the_return_value } + ## + @unittest.skip("demonstrating skipping") + def test_calculate_scale_factors(self): + pass + + @unittest.skip("demonstrating skipping") + def test_default_actions(self): + pass + + @unittest.skip("demonstrating skipping") + def test_executionble(self): + pass + + @unittest.skip("demonstrating skipping") + def test_fight_style(self): + pass + + @unittest.skip("demonstrating skipping") + def test_fixed_time(self): + pass + + @unittest.skip("demonstrating skipping") + def test_html(self): + pass + + @unittest.skip("demonstrating skipping") + def test_iterations(self): + pass + + @unittest.skip("demonstrating skipping") + def test_log(self): + pass + + @unittest.skip("demonstrating skipping") + def test_name(self): + pass + + @unittest.skip("demonstrating skipping") + def test_optimize_expressions(self): + pass + + @unittest.skip("demonstrating skipping") + def test_ptr(self): + pass + + @unittest.skip("demonstrating skipping") + def test_ready_trigger(self): + pass + + @unittest.skip("demonstrating skipping") + def test_simc_arguments(self): + pass + + @unittest.skip("demonstrating skipping") + def test_target_error(self): + pass + + @unittest.skip("demonstrating skipping") + def test_threads(self): + pass + + + + +if __name__ == '__main__': + unittest.main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5c032e7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +# Lib with World of Warcraft game data for simulations and some input checks for SimulationCraft. +-e git+https://github.com/Bloodmallet/simc_support.git@1.0.1#egg=simc_support diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ebd523e --- /dev/null +++ b/setup.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python + +from setuptools import setup + + +setup(name='bloodytools', + version='1.0', + author='Bloodmallet(EU)', + author_email='kruse.peter.1990@gmail.com', + description='Allows multiple ways of automated data generation via SimulationCraft for World of Warcraft.', + url='https://github.com/Bloodmallet/bloodytools', + packages=['bloodytools'], + package_data={ + "": ["*.md", ], + }, + python_requires='>3.5', + license='GNU GENERAL PUBLIC LICENSE', +) From 4ef478a958d6c6459b31e8a6486ae11eedabc2f2 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Fri, 2 Feb 2018 18:14:14 +0100 Subject: [PATCH 02/65] add more tests, fix variable names, fix some method's behaviour --- .../simulation_objecs/simulation_objects.py | 278 ++++++++---------- .../simulation_objects_tests.py | 274 +++++++++++++---- 2 files changed, 338 insertions(+), 214 deletions(-) diff --git a/bloodytools/simulation_objecs/simulation_objects.py b/bloodytools/simulation_objecs/simulation_objects.py index 8c7f56e..21704d8 100644 --- a/bloodytools/simulation_objecs/simulation_objects.py +++ b/bloodytools/simulation_objecs/simulation_objects.py @@ -11,76 +11,27 @@ class Error( Exception ): """Base class for exceptions in this module""" - pass + def __init__( self, message): + self.message = message class AlreadySetError( Error ): - """ - Exception raised if a value already exists. - - Attributes: - value -- value which was tried to set - message -- explanation of the error - """ - def __init__( self, value, message ): - self.value = value - self.message = message - logger.exception(self.message) + pass class InputError( Error ): - """ - Exception raised for errors in the input. - - Attributes: - expression -- input expression in which the error occurred - message -- explanation of the error - """ - def __init__( self, expression, message ): - self.expression = expression - self.message = message - logger.exception(self.message) + pass class NotSetYetError( Error ): - """ - Exception raised when simulations aren't done yet. - - Attributes: - value -- value which was tried to get - message -- explanation of the error - """ - def __init__( self, value, message ): - self.value = value - self.message = message - logger.exception(self.message) + pass class StillInProgressError( Error ): - """ - Exception raised when simulation is still in progress. - - Attributes: - value -- value which was tried to get - message -- explanation of the error - """ - def __init__( self, value, message ): - self.value = value - self.message = message - logger.exception(self.message) - - -class SimulationError(object): - """ - Exception raised when simulations aren't done yet. + pass - Attributes: - value -- value which was tried to get - message -- explanation of the error - """ - def __init__(self, simulation_output): - super(SimulationError, self).__init__() - self.simulation_output = simulation_output +class SimulationError( Error ): + pass class simulation_data(): @@ -97,7 +48,7 @@ def __init__( self, html="", iterations="250000", log="0", - name=None, + name="", optimize_expressions="1", ptr="0", ready_trigger="1", @@ -108,22 +59,22 @@ def __init__( self, super( simulation_data, self ).__init__() # simc setting to calculate scale factors (stat weights) - self.calculate_scale_factors = calculate_scale_factors - + if calculate_scale_factors == "0" or calculate_scale_factors == "1": + self.calculate_scale_factors = calculate_scale_factors + else: + self.calculate_scale_factors = "0" # simc setting to manage default apl usage - logger.debug("Initialising default_actions.") - self.default_actions = default_actions - + if default_actions == "0" or default_actions == "1": + self.default_actions = default_actions + else: + self.default_actions = "1" # simc setting to manage the accuracy of used apl lines (leave it at 1.0) - logger.debug("Initialising default_skill.") try: self.default_skill = str( float( default_skill ) ) except Exception as e: logger.error( "{} -- Using default value instead.".format( e ) ) self.default_skill = "1.0" - # describes the location and type of the simc executable - logger.debug("Initialising executionable.") # if no value was set, determine a standard value if executionable == None: if sys.platform == 'win32': @@ -138,36 +89,51 @@ def __init__( self, except Exception as e: logger.error( e ) raise e - # simc setting to determine the fight style - logger.debug("Initialising fight_style.") if fight_style == "custom" or simc_checks.is_fight_style( fight_style ): self.fight_style = fight_style else: logger.warning( "{} -- Using default value instead.".format( fight_style ) ) self.fight_style = "patchwerk" - # simc setting to enable/diable the fixed fight length - self.fixed_time = fixed_time + if fixed_time == "0" or fixed_time == "1": + self.fixed_time = fixed_time + else: + self.fixed_time = "1" # simc setting to enable html output - self.html = html + if type(html) == str: + self.html = html + else: + self.html = "" # simc setting to determine the maximum number of run iterations # (target_error and iterations determine the actually simulated # iterations count) self.iterations = str( int( iterations ) ) # simc setting to enable/disable a log file - self.log = log + if log == "0" or log == "1": + self.log = log + else: + self.log = "0" # optional name for the data if not name: self.name = uuid.uuid4() else: self.name = name # simc setting to enable/disable optimize expressions - self.optimize_expressions = optimize_expressions + if optimize_expressions == "0" or optimize_expressions == "1": + self.optimize_expressions = optimize_expressions + else: + self.optimize_expressions = "1" # simc setting to enable/disable ptr data - self.ptr = ptr + if ptr == "0" or ptr == "1": + self.ptr = ptr + else: + self.ptr = "0" # simc setting to enable/disable ready_trigger - self.ready_trigger = ready_trigger + if ready_trigger == "0" or ready_trigger == "1": + self.ready_trigger = ready_trigger + else: + self.ready_trigger = "1" # specific data to be run, like talent combinations, specific gear or # traits if type( simc_arguments ) == list or simc_arguments == []: @@ -175,11 +141,21 @@ def __init__( self, else: self.simc_arguments = [simc_arguments] # simc setting to determine the target_error - self.target_error = target_error + try: + self.target_error = str( float( target_error ) ) + except Exception as e: + self.target_error = "0.1" # simc setting to determine the number of used threads, empty string uses # all available - self.threads = threads + if type(threads) == int or type(threads) == str or type(threads) == float: + try: + self.threads = str( int( float( threads ) ) ) + except Exception as e: + self.threads = "" + else: + self.threads = "" + # set independant default values # creation time of the simulation object self.so_creation_time = datetime.datetime.utcnow() # simulation dps result @@ -194,7 +170,7 @@ def __init__( self, self.so_simulation_start_time = None - def is_equal(self, simulation_data): + def is_equal(self, simulation_instance): """ @brief Determines if the current and given simulation_data share the same base. The following attributes are considered base: @@ -203,39 +179,42 @@ def is_equal(self, simulation_data): optimize_expressions, ptr, ready_trigger, target_error, threads @param self The object - @param simulation_data The other simulation data + @param simulation_instance The other simulation data @return True if equal base values, False otherwise. """ - if self.calculate_scale_factors != simulation_data.calculate_scale_factors: - return False - if self.default_actions != simulation_data.default_actions: - return False - if self.default_skill != simulation_data.default_skill: - return False - if self.executionable != simulation_data.executionable: - return False - if self.fight_style != simulation_data.fight_style: - return False - if self.fixed_time != simulation_data.fixed_time: - return False - if self.html != simulation_data.html: - return False - if self.iterations != simulation_data.iterations: - return False - if self.log != simulation_data.log: - return False - if self.optimize_expressions != simulation_data.optimize_expressions: - return False - if self.ptr != simulation_data.ptr: - return False - if self.ready_trigger != simulation_data.ready_trigger: - return False - if self.target_error != simulation_data.target_error: - return False - if self.threads != simulation_data.threads: - return False - return True + try: + if self.calculate_scale_factors != simulation_instance.calculate_scale_factors: + return False + if self.default_actions != simulation_instance.default_actions: + return False + if self.default_skill != simulation_instance.default_skill: + return False + if self.executionable != simulation_instance.executionable: + return False + if self.fight_style != simulation_instance.fight_style: + return False + if self.fixed_time != simulation_instance.fixed_time: + return False + if self.html != simulation_instance.html: + return False + if self.iterations != simulation_instance.iterations: + return False + if self.log != simulation_instance.log: + return False + if self.optimize_expressions != simulation_instance.optimize_expressions: + return False + if self.ptr != simulation_instance.ptr: + return False + if self.ready_trigger != simulation_instance.ready_trigger: + return False + if self.target_error != simulation_instance.target_error: + return False + if self.threads != simulation_instance.threads: + return False + return True + except Exception as e: + False def get_dps( self ): @@ -246,12 +225,7 @@ def get_dps( self ): @return The dps. """ - if self.dps: - return int( self.dps ) - if self.so_simulation_start_time < datetime.datetime.utcnow() and not self.so_simulation_end_time: - raise StillInProgressError( "dps", "Dps simulation is still in progress." ) - else: - raise NotSetYetError( "dps", "No dps was saved yet." ) + return self.dps def set_dps( self, dps, external=True ): @@ -267,15 +241,14 @@ def set_dps( self, dps, external=True ): """ # set external_simulation flag, defaults to True, so external simulations # don't need to pay attention to this - try: - self.external_simulation = bool( external ) - except Exception as e: - raise e + if type( external ) == bool: + self.external_simulation = external + else: + raise TypeError # raise AlreadySetError if one tries to overwrite previously set data if self.dps: - raise AlreadySetError( "dps", - "A value for dps was already set to {}.".format( self.get_dps() ) ) + raise AlreadySetError( "A value for dps was already set to {}.".format( self.get_dps() ) ) try: # slightly more robust than simply cast to int, due to possible float @@ -284,22 +257,28 @@ def set_dps( self, dps, external=True ): except Exception as e: raise e + if self.so_simulation_end_time != None: + self.so_simulation_end_time = datetime.datetime.utcnow() + return True - def get_avg( self, simulation_data ): + def get_avg( self, simulation_instance ): """ - @brief Gets the average of the current the given simulation_data. + @brief Gets the average of the current the given simulation_instance. - @param self The object - @param simulation_data The simulation data + @param self The object + @param simulation_instance The simulation data @return The average as int. """ - return int( ( self.get_dps() + simulation_data.get_dps() ) / 2 ) + if self.get_dps() and simulation_instance.get_dps(): + return int( ( self.get_dps() + simulation_instance.get_dps() ) / 2 ) + else: + return None - def get_simulation_duation( self ): + def get_simulation_duration( self ): """ @brief Return the time, the simulation took. Will raise NotDoneYetError if simulation didn't start or end yet. @@ -308,14 +287,10 @@ def get_simulation_duation( self ): @return The simulation duation. """ - if not self.so_simulation_start_time: - raise NotSetYetError( "so_simulation_start_time", - "Simulation didn't start yet. No simulation duration possible." ) - if not self.so_simulation_end_time: - raise NotSetYetError( "so_simulation_end_time", - "Simulation isn't done yet. No simulation duration possible." ) - - return self.so_simulation_end_time - self.so_simulation_start_time + if self.so_simulation_start_time and self.so_simulation_end_time: + return self.so_simulation_end_time - self.so_simulation_start_time + else: + None def set_full_report( self, report ): @@ -331,7 +306,7 @@ def set_full_report( self, report ): self.full_report = report return True else: - raise TypeError("Report as type {} found but string expected.".format( type( report ) ) ) + raise TypeError("Report as type {} found but string was expected.".format( type( report ) ) ) def set_simulation_end_time( self ): @@ -351,8 +326,7 @@ def set_simulation_end_time( self ): else: return True else: - raise AlreadySetError( "so_simulation_end_time", - "End time was already set. Setting it twice is not allowed." ) + raise AlreadySetError( "Simulation end time was already set. Setting it twice is not allowed." ) def set_simulation_start_time( self ): @@ -480,22 +454,19 @@ class simulation_group(): SimulationCrafts own profilesets feature. Dps values are saved in the simulation_data. """ - def __init__( self, simulation_data, name="" ): + def __init__( self, simulation_instance, name="" ): super( simulation_group, self ).__init__() - if type( simulation_data ) == list: - self.profiles = simulation_data - elif type( simulation_data ) == simulation_data: - self.profiles = [ simulation_data ] + if type( simulation_instance ) == list: + self.profiles = simulation_instance + elif type( simulation_instance ) == simulation_instance: + self.profiles = [ simulation_instance ] else: - logger.error( "Wrong type {} of simulation_data (need list of or single simulation_data).".format( type( simulation_data ) ) ) - raise TypeError( "simulation_group initiation", - "Wrong type " + str( type( simulation_data ) ) + " of simulation_data (need list/single simulation_data)." ) + raise TypeError( "Simulation_instance has wrong type '{}' (needed list or single simulation_data).".format(type(simulation_instance)) ) # optional name of the simulation_group self.name = name if not selfcheck(): - raise InputError( "simulation_group", - "simulation_data base data wasn't coherent.") + raise InputError( "simulation_instance base data wasn't coherent." ) def selfcheck( self ): @@ -537,22 +508,21 @@ def simulate( self ): - def add( self, simulation_data ): + def add( self, simulation_instance ): """ - @brief Add another simulation_data object to the group + @brief Add another simulation_instance object to the group @param self The object - @param simulation_data The simulation information + @param simulation_instance The simulation information @return True, if adding data was successfull. Exception otherwise. """ - if type( simulation_data ) == simulation_data: + if type( simulation_instance ) == simulation_data: try: - self.profiles.append( simulation_data ) + self.profiles.append( simulation_instance ) except Exception as e: raise e else: return True else: - raise TypeError( "simulation_group.add", - "Wrong type of simulation_data (need simulation_data)." ) + raise TypeError( "Simulation_instance has wrong type '{}' (needed simulation_data).".format(type(simulation_instance)) ) diff --git a/bloodytools/simulation_objecs/simulation_objects_tests.py b/bloodytools/simulation_objecs/simulation_objects_tests.py index 4cad956..d815b96 100644 --- a/bloodytools/simulation_objecs/simulation_objects_tests.py +++ b/bloodytools/simulation_objecs/simulation_objects_tests.py @@ -2,6 +2,7 @@ import logging import unittest import uuid +import time import simulation_objects @@ -9,7 +10,7 @@ ## ## @brief Simple tests for the handling of init values for objects of the -## simulation_data class +## simulation_data class. ## class TestSimulationDataInit(unittest.TestCase): @@ -21,7 +22,7 @@ class TestSimulationDataInit(unittest.TestCase): ## @return Nothing ## def tearDown(self): - self.simulation_data = None + self.sd = None ## ## @brief Test all available and left empty input values for their @@ -32,97 +33,250 @@ def tearDown(self): ## @return { description_of_the_return_value } ## def test_empty(self): - self.simulation_data = simulation_objects.simulation_data() - self.assertEqual( self.simulation_data.calculate_scale_factors, "0" ) - self.assertEqual( self.simulation_data.default_actions, "1" ) - self.assertEqual( self.simulation_data.default_skill, "1.0" ) - self.assertEqual( self.simulation_data.fight_style, "patchwerk" ) - self.assertEqual( self.simulation_data.fixed_time, "1" ) - self.assertEqual( self.simulation_data.html, "" ) - self.assertEqual( self.simulation_data.iterations, "250000" ) - self.assertEqual( self.simulation_data.log, "0" ) - self.assertNotEqual( self.simulation_data.name, None ) - self.assertEqual( self.simulation_data.optimize_expressions, "1" ) - self.assertEqual( self.simulation_data.ptr, "0" ) - self.assertEqual( self.simulation_data.ready_trigger, "1" ) - self.assertEqual( self.simulation_data.simc_arguments, [] ) - self.assertEqual( self.simulation_data.target_error, "0.1" ) - self.assertEqual( self.simulation_data.threads, "" ) - self.assertEqual( type( self.simulation_data.so_creation_time), datetime.datetime ) - self.assertEqual( self.simulation_data.external_simulation, False ) - self.assertEqual( self.simulation_data.full_report, None ) - self.assertEqual( self.simulation_data.so_simulation_end_time, None ) - self.assertEqual( self.simulation_data.so_simulation_start_time, None ) - - - - ## - ## @brief Test input checks of calculate_scale_factor. + self.sd = simulation_objects.simulation_data() + self.assertEqual( self.sd.calculate_scale_factors, "0" ) + self.assertEqual( self.sd.default_actions, "1" ) + self.assertEqual( self.sd.default_skill, "1.0" ) + self.assertEqual( self.sd.fight_style, "patchwerk" ) + self.assertEqual( self.sd.fixed_time, "1" ) + self.assertEqual( self.sd.html, "" ) + self.assertEqual( self.sd.iterations, "250000" ) + self.assertEqual( self.sd.log, "0" ) + self.assertNotEqual( self.sd.name, None ) + self.assertEqual( self.sd.optimize_expressions, "1" ) + self.assertEqual( self.sd.ptr, "0" ) + self.assertEqual( self.sd.ready_trigger, "1" ) + self.assertEqual( self.sd.simc_arguments, [] ) + self.assertEqual( self.sd.target_error, "0.1" ) + self.assertEqual( self.sd.threads, "" ) + self.assertEqual( type( self.sd.so_creation_time), datetime.datetime ) + self.assertEqual( self.sd.external_simulation, False ) + self.assertEqual( self.sd.full_report, None ) + self.assertEqual( self.sd.so_simulation_end_time, None ) + self.assertEqual( self.sd.so_simulation_start_time, None ) + + ## + ## @brief Test input checks of calculate_scale_factors. ## ## @param self The object ## ## @return { description_of_the_return_value } ## - @unittest.skip("demonstrating skipping") def test_calculate_scale_factors(self): - pass + self.sd = simulation_objects.simulation_data( calculate_scale_factors="1" ) + self.assertEqual( self.sd.calculate_scale_factors, "1" ) + self.assertNotEqual( self.sd.calculate_scale_factors, "0" ) + self.assertNotEqual( self.sd.calculate_scale_factors, str ) + self.assertNotEqual( self.sd.calculate_scale_factors, list ) + self.assertNotEqual( self.sd.calculate_scale_factors, dict ) + self.assertNotEqual( self.sd.calculate_scale_factors, int ) - @unittest.skip("demonstrating skipping") + ## + ## @brief Test input checks of default_actions. + ## + ## @param self The object + ## + ## @return { description_of_the_return_value } + ## def test_default_actions(self): - pass + self.sd = simulation_objects.simulation_data( default_actions="1" ) + self.assertEqual( self.sd.default_actions, "1" ) + self.assertNotEqual( self.sd.default_actions, "0" ) + self.assertNotEqual( self.sd.default_actions, str ) + self.assertNotEqual( self.sd.default_actions, list ) + self.assertNotEqual( self.sd.default_actions, dict ) + self.assertNotEqual( self.sd.default_actions, int ) - @unittest.skip("demonstrating skipping") - def test_executionble(self): - pass - - @unittest.skip("demonstrating skipping") def test_fight_style(self): - pass + self.sd = simulation_objects.simulation_data( fight_style="helterskelter" ) + self.assertEqual( self.sd.fight_style, "helterskelter" ) + self.assertNotEqual( self.sd.fight_style, "0" ) + self.assertNotEqual( self.sd.fight_style, str ) + self.assertNotEqual( self.sd.fight_style, list ) + self.assertNotEqual( self.sd.fight_style, dict ) + self.assertNotEqual( self.sd.fight_style, int ) + # simc_checks needs improvements to catch this + #self.sd = None + #self.sd = simulation_objects.simulation_data( fight_style=1234 ) + #self.assertEqual( self.sd.fight_style, "patchwerk" ) - @unittest.skip("demonstrating skipping") def test_fixed_time(self): - pass + self.sd = simulation_objects.simulation_data( fixed_time="1" ) + self.assertEqual( self.sd.fixed_time, "1" ) + self.assertNotEqual( self.sd.fixed_time, "0" ) + self.assertNotEqual( self.sd.fixed_time, str ) + self.assertNotEqual( self.sd.fixed_time, list ) + self.assertNotEqual( self.sd.fixed_time, dict ) + self.assertNotEqual( self.sd.fixed_time, int ) - @unittest.skip("demonstrating skipping") def test_html(self): - pass + self.sd = simulation_objects.simulation_data( html="testiger.html" ) + self.assertEqual( self.sd.html, "testiger.html" ) + self.assertNotEqual( self.sd.html, "" ) + self.assertNotEqual( self.sd.html, str ) + self.assertNotEqual( self.sd.html, list ) + self.assertNotEqual( self.sd.html, dict ) + self.assertNotEqual( self.sd.html, int ) - @unittest.skip("demonstrating skipping") def test_iterations(self): - pass + self.sd = simulation_objects.simulation_data( iterations="15000" ) + self.assertEqual( self.sd.iterations, "15000" ) + self.assertNotEqual( self.sd.iterations, "" ) + self.assertNotEqual( self.sd.iterations, str ) + self.assertNotEqual( self.sd.iterations, list ) + self.assertNotEqual( self.sd.iterations, dict ) + self.assertNotEqual( self.sd.iterations, int ) + self.sd = None + self.sd = simulation_objects.simulation_data( iterations=15000 ) + self.assertEqual( self.sd.iterations, "15000" ) + self.sd = None + self.sd = simulation_objects.simulation_data( iterations=15000.75 ) + self.assertEqual( self.sd.iterations, "15000" ) - @unittest.skip("demonstrating skipping") def test_log(self): - pass + self.sd = simulation_objects.simulation_data( log="1" ) + self.assertEqual( self.sd.log, "1" ) + self.assertNotEqual( self.sd.log, "" ) + self.assertNotEqual( self.sd.log, str ) + self.assertNotEqual( self.sd.log, list ) + self.assertNotEqual( self.sd.log, dict ) + self.assertNotEqual( self.sd.log, int ) + self.sd = None + self.sd = simulation_objects.simulation_data( iterations=15000 ) + self.assertEqual( self.sd.log, "0" ) - @unittest.skip("demonstrating skipping") def test_name(self): - pass + self.sd = simulation_objects.simulation_data( name="" ) + self.assertNotEqual( self.sd.name, "" ) + self.assertEqual( type( self.sd.name ), uuid.UUID ) + self.sd = None + self.sd = simulation_objects.simulation_data( name="Borke" ) + self.assertEqual( self.sd.name, "Borke" ) - @unittest.skip("demonstrating skipping") def test_optimize_expressions(self): - pass + self.sd = simulation_objects.simulation_data( optimize_expressions="1" ) + self.assertEqual( self.sd.optimize_expressions, "1" ) + self.assertNotEqual( self.sd.optimize_expressions, "0" ) + self.assertNotEqual( self.sd.optimize_expressions, str ) + self.assertNotEqual( self.sd.optimize_expressions, list ) + self.assertNotEqual( self.sd.optimize_expressions, dict ) + self.assertNotEqual( self.sd.optimize_expressions, int ) + self.sd = None + self.sd = simulation_objects.simulation_data( optimize_expressions=["1"] ) + self.assertEqual( self.sd.optimize_expressions, "1" ) - @unittest.skip("demonstrating skipping") def test_ptr(self): - pass + self.sd = simulation_objects.simulation_data( ptr="1" ) + self.assertEqual( self.sd.ptr, "1" ) + self.assertNotEqual( self.sd.ptr, "0" ) + self.assertNotEqual( self.sd.ptr, str ) + self.assertNotEqual( self.sd.ptr, list ) + self.assertNotEqual( self.sd.ptr, dict ) + self.assertNotEqual( self.sd.ptr, int ) + self.sd = None + self.sd = simulation_objects.simulation_data( ptr=["1"] ) + self.assertEqual( self.sd.ptr, "0" ) - @unittest.skip("demonstrating skipping") def test_ready_trigger(self): - pass + self.sd = simulation_objects.simulation_data( ready_trigger="1" ) + self.assertEqual( self.sd.ready_trigger, "1" ) + self.assertNotEqual( self.sd.ready_trigger, "0" ) + self.assertNotEqual( self.sd.ready_trigger, str ) + self.assertNotEqual( self.sd.ready_trigger, list ) + self.assertNotEqual( self.sd.ready_trigger, dict ) + self.assertNotEqual( self.sd.ready_trigger, int ) + self.sd = None + self.sd = simulation_objects.simulation_data( ready_trigger=["1"] ) + self.assertEqual( self.sd.ready_trigger, "1" ) - @unittest.skip("demonstrating skipping") def test_simc_arguments(self): - pass + self.sd = simulation_objects.simulation_data( simc_arguments="1" ) + self.assertNotEqual( self.sd.simc_arguments, "1" ) + self.assertEqual( self.sd.simc_arguments, ["1"] ) + self.sd = None + self.sd = simulation_objects.simulation_data( simc_arguments=["a", "b"]) + self.assertEqual( self.sd.simc_arguments, ["a", "b"]) - @unittest.skip("demonstrating skipping") def test_target_error(self): - pass + self.sd = simulation_objects.simulation_data( target_error="0.5" ) + self.assertEqual( self.sd.target_error, "0.5" ) + self.assertNotEqual( self.sd.target_error, ["1"] ) + self.sd = None + self.sd = simulation_objects.simulation_data( target_error=["a", "b"]) + self.assertEqual( self.sd.target_error, "0.1") - @unittest.skip("demonstrating skipping") def test_threads(self): - pass + self.sd = simulation_objects.simulation_data( threads="1.5" ) + self.assertEqual( self.sd.threads, "1" ) + self.assertNotEqual( self.sd.threads, "1.5" ) + self.sd = None + self.sd = simulation_objects.simulation_data( threads=["a", "b"]) + self.assertEqual( self.sd.threads, "") + +## +## @brief Tests for all methods of the simulation_data class. +## +class TestSimulationDataMethods(unittest.TestCase): + def setUp(self): + self.sd = simulation_objects.simulation_data() + + def tearDown(self): + self.sd = None + + def test_is_equal(self): + sd1 = self.sd + sd2 = simulation_objects.simulation_data() + self.assertTrue( sd1.is_equal( sd2 ) ) + self.assertTrue( sd2.is_equal( sd1 ) ) + self.assertTrue( sd1.is_equal( sd1 ) ) + sd3 = simulation_objects.simulation_data( iterations="124153", threads="8" ) + self.assertFalse( sd3.is_equal( sd1 ) ) + self.assertFalse( sd3.is_equal( "Döner" ) ) + + def test_get_dps(self): + self.assertEqual(self.sd.get_dps(), None) + self.sd.set_dps(12345.8) + self.assertTrue(self.sd.get_dps(), 12345) + + def test_set_dps(self): + self.assertTrue( self.sd.set_dps("123") ) + self.assertEqual(self.sd.dps, 123) + with self.assertRaises(simulation_objects.AlreadySetError): + self.sd.set_dps( 5678.9 ) + with self.assertRaises( TypeError ): + self.sd.set_dps( "5678", external="Hallo" ) + + self.sd = None + self.sd = simulation_objects.simulation_data() + with self.assertRaises( TypeError ): + self.sd.set_dps("Bonjour", external="Bonsoir") + self.assertEqual( self.sd.get_dps(), None ) + with self.assertRaises( ValueError ): + self.sd.set_dps( "Bonjour", external=False ) + self.assertEqual( self.sd.get_dps(), None ) + + def test_get_avg(self): + sd = simulation_objects.simulation_data() + sd.set_dps( 100 ) + self.sd.set_dps( 50 ) + self.assertEqual( sd.get_avg( self.sd ), 75 ) + sd_empty = simulation_objects.simulation_data() + self.assertEqual( self.sd.get_avg( sd_empty ), None ) + + def test_get_simulation_duration(self): + self.assertEqual( self.sd.get_simulation_duration(), None ) + self.sd.so_simulation_start_time = datetime.datetime.utcnow() + time.sleep( 0.005 ) + self.assertEqual( self.sd.get_simulation_duration(), None ) + self.sd.so_simulation_end_time = datetime.datetime.utcnow() + self.assertNotEqual( self.sd.get_simulation_duration(), None ) + def test_set_full_report(self): + with self.assertRaises( TypeError ): + self.sd.set_full_report( 1234 ) + report = str( uuid.uuid4() ) + self.assertTrue( self.sd.set_full_report( report ) ) + self.assertEqual( self.sd.full_report, report ) From 018ec8f0ce84b8c2ec990bff506a6faec2a837bb Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Sat, 3 Feb 2018 12:56:57 +0100 Subject: [PATCH 03/65] Add travis CI file --- .travis.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..7974df1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +# This script is used by the Travis-CI (continuous integration) testing +# framework to run Bloodytools tests with every GitHub push or pull-request. +language: python + +python: + - "3.6" + +branches: + only: + - dev + +script: + # Test Bloodytools. + - python bloodytools/simulation_objects/simulation_objects_tests.py + From 65a666c24e3e2e0d51e41e627af54def5b42ee64 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Sat, 3 Feb 2018 13:00:56 +0100 Subject: [PATCH 04/65] try to fix travis --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7974df1..8536fa0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,5 +11,7 @@ branches: script: # Test Bloodytools. - - python bloodytools/simulation_objects/simulation_objects_tests.py + - ls + - cd ./bloodytools/ + - python ./bloodytools/simulation_objects/simulation_objects_tests.py From 4157df35bd58bd13425622ab81f7fdcbd3e961e0 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Sat, 3 Feb 2018 13:06:21 +0100 Subject: [PATCH 05/65] travis plz --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8536fa0..a519fd2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,5 @@ branches: script: # Test Bloodytools. - - ls - - cd ./bloodytools/ - python ./bloodytools/simulation_objects/simulation_objects_tests.py From 7f3ff31d878983e2e0f5226cf04349e167d93942 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Sat, 3 Feb 2018 13:09:28 +0100 Subject: [PATCH 06/65] travis, what are you doing? --- .travis.yml | 3 +++ bloodytools/simulation_objecs/simulation_objects.py | 2 ++ bloodytools/simulation_objecs/simulation_objects_tests.py | 2 ++ 3 files changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index a519fd2..4618c43 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,5 +11,8 @@ branches: script: # Test Bloodytools. + - ls + - ls ./bloodytools/ + - ls ./bloodytools/simulation_objects/ - python ./bloodytools/simulation_objects/simulation_objects_tests.py diff --git a/bloodytools/simulation_objecs/simulation_objects.py b/bloodytools/simulation_objecs/simulation_objects.py index 21704d8..226f0e7 100644 --- a/bloodytools/simulation_objecs/simulation_objects.py +++ b/bloodytools/simulation_objecs/simulation_objects.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + # date data import datetime # wow game data and simc input checks diff --git a/bloodytools/simulation_objecs/simulation_objects_tests.py b/bloodytools/simulation_objecs/simulation_objects_tests.py index d815b96..eccdb5b 100644 --- a/bloodytools/simulation_objecs/simulation_objects_tests.py +++ b/bloodytools/simulation_objecs/simulation_objects_tests.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + import datetime import logging import unittest From c170a545b83fb6268ffb5cacdd02fc08fe70e598 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Sat, 3 Feb 2018 13:15:53 +0100 Subject: [PATCH 07/65] dwindling sanity --- .travis.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4618c43..44427dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,9 @@ branches: script: # Test Bloodytools. - ls - - ls ./bloodytools/ - - ls ./bloodytools/simulation_objects/ - - python ./bloodytools/simulation_objects/simulation_objects_tests.py + - cd ./bloodytools/ + - ls + - cd ./simulation_objects/ + - ls + - python ./simulation_objects_tests.py From 3593ee0cc301942b2082d0286abe38413598bb74 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Sat, 3 Feb 2018 13:20:02 +0100 Subject: [PATCH 08/65] travis...I'm sorry, I was blind! --- .travis.yml | 7 +------ .../simulation_objects.py | 0 .../simulation_objects_tests.py | 0 3 files changed, 1 insertion(+), 6 deletions(-) rename bloodytools/{simulation_objecs => simulation_objects}/simulation_objects.py (100%) rename bloodytools/{simulation_objecs => simulation_objects}/simulation_objects_tests.py (100%) diff --git a/.travis.yml b/.travis.yml index 44427dd..a519fd2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,10 +11,5 @@ branches: script: # Test Bloodytools. - - ls - - cd ./bloodytools/ - - ls - - cd ./simulation_objects/ - - ls - - python ./simulation_objects_tests.py + - python ./bloodytools/simulation_objects/simulation_objects_tests.py diff --git a/bloodytools/simulation_objecs/simulation_objects.py b/bloodytools/simulation_objects/simulation_objects.py similarity index 100% rename from bloodytools/simulation_objecs/simulation_objects.py rename to bloodytools/simulation_objects/simulation_objects.py diff --git a/bloodytools/simulation_objecs/simulation_objects_tests.py b/bloodytools/simulation_objects/simulation_objects_tests.py similarity index 100% rename from bloodytools/simulation_objecs/simulation_objects_tests.py rename to bloodytools/simulation_objects/simulation_objects_tests.py From ceb6b05190670aecaa4bb795e39d17310fea33f1 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Sat, 3 Feb 2018 13:22:36 +0100 Subject: [PATCH 09/65] add Build status icon --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4d26513..b231943 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ Bloody( tools ) =========== +[![Build Status](https://travis-ci.org/Bloodmallet/bloodytools.svg?branch=dev)](https://travis-ci.org/Bloodmallet/bloodytools) > Automatation tool for several different aspects of all dps and some tank specs in World of Warcraft using SimulationCraft. From 4f543a93ae3f99a11dceffe1d2175d858f63ebb1 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Sat, 3 Feb 2018 13:48:22 +0100 Subject: [PATCH 10/65] add discord notifications to travis and deactivate email --- .travis.yml | 19 ++++++++++++++++++- fail.sh | 6 ++++++ success.sh | 6 ++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 fail.sh create mode 100644 success.sh diff --git a/.travis.yml b/.travis.yml index a519fd2..00c640e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,23 @@ branches: - dev script: - # Test Bloodytools. + # Test simulation_objects. - python ./bloodytools/simulation_objects/simulation_objects_tests.py +# makes travis send a notification to discord with these values +before_script: + - export AUTHOR_NAME="$(git log -1 $TRAVIS_COMMIT --pretty="%aN")" + +# makes travis send a notification to discord +after_success: + - chmod +x success.sh + - ./success.sh + +# makes travis send a notification to discord +after_failure: + - chmod +x fail.sh + - ./fail.sh + +# no email notifications, this shall be handled/viewed via discord +notifications: + email: false diff --git a/fail.sh b/fail.sh new file mode 100644 index 0000000..178c1ed --- /dev/null +++ b/fail.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +echo 'Sending Discord Webhook'; +export BACKTICK='`'; +export TIMESTAMP=$(date --utc +%FT%TZ); +export COMMIT_FORMATTED="[$BACKTICK${TRAVIS_COMMIT:0:7}$BACKTICK](https://github.com/$REPO_OWNER/$REPO_NAME/commit/$TRAVIS_COMMIT)"; +curl -v -H User-Agent:bot -H Content-Type:application/json -d '{"avatar_url":"https://i.imgur.com/kOfUGNS.png","username":"Travis CI","embeds":[{"author":{"name":"Build #'"$TRAVIS_BUILD_NUMBER"' Failed - '"$AUTHOR_NAME"'","url":"https://travis-ci.org/'"$REPO_OWNER"'/'"$REPO_NAME"'/builds/'"$TRAVIS_BUILD_ID"'"},"url":"https://github.com/'"$REPO_OWNER"'/'"$REPO_NAME"'/commit/'"$TRAVIS_COMMIT"'","title":"['"$TRAVIS_REPO_SLUG"':'"$TRAVIS_BRANCH"'] ","color":16711680,"fields":[{"name":"_ _", "value": "'"$COMMIT_FORMATTED"' - '"$TRAVIS_COMMIT_MESSAGE"'"}],"timestamp":"'"$TIMESTAMP"'","footer":{"text":"Travis CI"}}]}' $DISCORD_WEBHOOK_URL; diff --git a/success.sh b/success.sh new file mode 100644 index 0000000..1a18871 --- /dev/null +++ b/success.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +echo 'Sending Discord Webhook'; +export BACKTICK='`'; +export TIMESTAMP=$(date --utc +%FT%TZ); +export COMMIT_FORMATTED="[$BACKTICK${TRAVIS_COMMIT:0:7}$BACKTICK](https://github.com/$REPO_OWNER/$REPO_NAME/commit/$TRAVIS_COMMIT)"; +curl -v -H User-Agent:bot -H Content-Type:application/json -d '{"avatar_url":"https://i.imgur.com/kOfUGNS.png","username":"Travis CI","embeds":[{"author":{"name":"Build #'"$TRAVIS_BUILD_NUMBER"' Passed - '"$AUTHOR_NAME"'","url":"https://travis-ci.org/'"$REPO_OWNER"'/'"$REPO_NAME"'/builds/'"$TRAVIS_BUILD_ID"'"},"url":"https://github.com/'"$REPO_OWNER"'/'"$REPO_NAME"'/commit/'"$TRAVIS_COMMIT"'","title":"['"$TRAVIS_REPO_SLUG"':'"$TRAVIS_BRANCH"'] ","color":65280,"fields":[{"name":"_ _", "value": "'"$COMMIT_FORMATTED"' - '"$TRAVIS_COMMIT_MESSAGE"'"}],"timestamp":"'"$TIMESTAMP"'","footer":{"text":"Travis CI"}}]}' $DISCORD_WEBHOOK_URL; From 5e8896b1dd64b045fd98034cb0e3e830b7bd6b4f Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Sat, 3 Feb 2018 14:18:55 +0100 Subject: [PATCH 11/65] add coveralls coverage tests --- .travis.yml | 5 +++++ README.md | 2 +- requirements-test.txt | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 requirements-test.txt diff --git a/.travis.yml b/.travis.yml index 00c640e..f625392 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,10 @@ branches: only: - dev +install: + - pip install -r ./requirements.txt + - pip install -r ./requirements-test.txt + script: # Test simulation_objects. - python ./bloodytools/simulation_objects/simulation_objects_tests.py @@ -21,6 +25,7 @@ before_script: after_success: - chmod +x success.sh - ./success.sh + - coveralls # makes travis send a notification to discord after_failure: diff --git a/README.md b/README.md index b231943..957c018 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ Bloody( tools ) =========== -[![Build Status](https://travis-ci.org/Bloodmallet/bloodytools.svg?branch=dev)](https://travis-ci.org/Bloodmallet/bloodytools) +[![Build Status](https://travis-ci.org/Bloodmallet/bloodytools.svg?branch=dev)](https://travis-ci.org/Bloodmallet/bloodytools) [![Coverage Status](https://coveralls.io/repos/github/Bloodmallet/bloodytools/badge.svg?branch=master)](https://coveralls.io/github/Bloodmallet/bloodytools?branch=master) > Automatation tool for several different aspects of all dps and some tank specs in World of Warcraft using SimulationCraft. diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 0000000..2d3572b --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,2 @@ +# necesary for coverage check +coveralls From 750306ed0f05dee649d2813046a06d82369e88b7 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Sat, 3 Feb 2018 14:46:43 +0100 Subject: [PATCH 12/65] try fixing coverage --- .travis.yml | 1 + requirements-test.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f625392..0c13c37 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ install: script: # Test simulation_objects. - python ./bloodytools/simulation_objects/simulation_objects_tests.py + - coverage run --source=bloodytools/simulation_objects simulation_objects_tests # makes travis send a notification to discord with these values before_script: diff --git a/requirements-test.txt b/requirements-test.txt index 2d3572b..f425260 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,2 +1,2 @@ -# necesary for coverage check +# necessary for coverage check coveralls From b3c04c392f1365f7c2192f584f9c6c9af748e982 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Sat, 3 Feb 2018 14:51:09 +0100 Subject: [PATCH 13/65] add path to coverage --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0c13c37..90a7f1a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ install: script: # Test simulation_objects. - python ./bloodytools/simulation_objects/simulation_objects_tests.py - - coverage run --source=bloodytools/simulation_objects simulation_objects_tests + - coverage run --source=bloodytools/simulation_objects ./bloodytools/simulation_objects/simulation_objects_tests # makes travis send a notification to discord with these values before_script: From b4e0e7bae76040b85f30ec35fad2fcef879a97d1 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Sat, 3 Feb 2018 14:58:51 +0100 Subject: [PATCH 14/65] add coverage to gitignore, maybe fix coverage path --- .gitignore | 1 + .travis.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ed9e95b..c9f8242 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ env35_pyqt build-versuch_zwei-Desktop_Qt_5_8_0_MSVC2015_64bit-Debug /env *.pyc +.coverage diff --git a/.travis.yml b/.travis.yml index 90a7f1a..2b47709 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ install: script: # Test simulation_objects. - python ./bloodytools/simulation_objects/simulation_objects_tests.py - - coverage run --source=bloodytools/simulation_objects ./bloodytools/simulation_objects/simulation_objects_tests + - coverage run --source=bloodytools/simulation_objects ./bloodytools/simulation_objects/simulation_objects_tests.py # makes travis send a notification to discord with these values before_script: From bc526f0c96fa1d002cd823e220aa8264a1b15c45 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Sat, 3 Feb 2018 15:19:43 +0100 Subject: [PATCH 15/65] fix coverage tag to correct branch, exclude test file from test coverage, don't test twice --- .travis.yml | 5 ++--- README.md | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2b47709..1918328 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,9 +14,8 @@ install: - pip install -r ./requirements-test.txt script: - # Test simulation_objects. - - python ./bloodytools/simulation_objects/simulation_objects_tests.py - - coverage run --source=bloodytools/simulation_objects ./bloodytools/simulation_objects/simulation_objects_tests.py + # Test simulation_objects and collect coverage information at the same time. + - coverage run --source=bloodytools/simulation_objects --omit=*_tests.py ./bloodytools/simulation_objects/simulation_objects_tests.py # makes travis send a notification to discord with these values before_script: diff --git a/README.md b/README.md index 957c018..b79ea23 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ Bloody( tools ) =========== -[![Build Status](https://travis-ci.org/Bloodmallet/bloodytools.svg?branch=dev)](https://travis-ci.org/Bloodmallet/bloodytools) [![Coverage Status](https://coveralls.io/repos/github/Bloodmallet/bloodytools/badge.svg?branch=master)](https://coveralls.io/github/Bloodmallet/bloodytools?branch=master) +[![Build Status](https://travis-ci.org/Bloodmallet/bloodytools.svg?branch=dev)](https://travis-ci.org/Bloodmallet/bloodytools) [![Coverage Status](https://coveralls.io/repos/github/Bloodmallet/bloodytools/badge.svg?branch=dev)](https://coveralls.io/github/Bloodmallet/bloodytools?branch=dev) > Automatation tool for several different aspects of all dps and some tank specs in World of Warcraft using SimulationCraft. From 9cdd03584557e5840881933221d55dbb3c5b276a Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Sat, 3 Feb 2018 15:25:25 +0100 Subject: [PATCH 16/65] let's try older python versions --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1918328..5fc3c62 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,11 @@ language: python python: - - "3.6" + - 3.3 + - 3.4 + - 3.5 + - 3.6 + - 2.7 branches: only: From 818080862b8b895759993299f13493b3d2acbc05 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Sat, 3 Feb 2018 15:29:59 +0100 Subject: [PATCH 17/65] deactivate old python versions for the time being --- .travis.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5fc3c62..ae37684 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,11 +3,12 @@ language: python python: - - 3.3 - - 3.4 - - 3.5 - 3.6 - - 2.7 + - 3.5 + # can maybe added later when tests for simc-support are added for older versions + #- 3.4 + #- 3.3 + #- 2.7 branches: only: From 9e1c3c53625a56a5605dbe8e70c63e74d0e333a8 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Sat, 3 Feb 2018 15:32:25 +0100 Subject: [PATCH 18/65] fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b79ea23..7befccd 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Bloody( tools ) =========== [![Build Status](https://travis-ci.org/Bloodmallet/bloodytools.svg?branch=dev)](https://travis-ci.org/Bloodmallet/bloodytools) [![Coverage Status](https://coveralls.io/repos/github/Bloodmallet/bloodytools/badge.svg?branch=dev)](https://coveralls.io/github/Bloodmallet/bloodytools?branch=dev) -> Automatation tool for several different aspects of all dps and some tank specs in World of Warcraft using SimulationCraft. +> Automation tool for several different aspects of all dps and some tank specs in World of Warcraft using SimulationCraft. ## Requirements You need the latest [SimulationCraft](http://downloads.simulationcraft.org/?C=M;O=D) version ([GitHub Repository](https://github.com/simulationcraft/simc)), [Python 3.5](https://www.python.org/downloads/) or newer and the module [simc_support](https://github.com/Bloodmallet/simc_support), which is handled in the requirements.txt. From ff76894550b29de3c3821cd94a38740352dd80a4 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Sat, 3 Feb 2018 15:44:00 +0100 Subject: [PATCH 19/65] enhance discord notification with python version --- .travis.yml | 1 + fail.sh | 2 +- success.sh | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ae37684..938632b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,7 @@ script: # makes travis send a notification to discord with these values before_script: - export AUTHOR_NAME="$(git log -1 $TRAVIS_COMMIT --pretty="%aN")" + - export PYTHON_VERSION="$(python --version)" # makes travis send a notification to discord after_success: diff --git a/fail.sh b/fail.sh index 178c1ed..c6e3f38 100644 --- a/fail.sh +++ b/fail.sh @@ -3,4 +3,4 @@ echo 'Sending Discord Webhook'; export BACKTICK='`'; export TIMESTAMP=$(date --utc +%FT%TZ); export COMMIT_FORMATTED="[$BACKTICK${TRAVIS_COMMIT:0:7}$BACKTICK](https://github.com/$REPO_OWNER/$REPO_NAME/commit/$TRAVIS_COMMIT)"; -curl -v -H User-Agent:bot -H Content-Type:application/json -d '{"avatar_url":"https://i.imgur.com/kOfUGNS.png","username":"Travis CI","embeds":[{"author":{"name":"Build #'"$TRAVIS_BUILD_NUMBER"' Failed - '"$AUTHOR_NAME"'","url":"https://travis-ci.org/'"$REPO_OWNER"'/'"$REPO_NAME"'/builds/'"$TRAVIS_BUILD_ID"'"},"url":"https://github.com/'"$REPO_OWNER"'/'"$REPO_NAME"'/commit/'"$TRAVIS_COMMIT"'","title":"['"$TRAVIS_REPO_SLUG"':'"$TRAVIS_BRANCH"'] ","color":16711680,"fields":[{"name":"_ _", "value": "'"$COMMIT_FORMATTED"' - '"$TRAVIS_COMMIT_MESSAGE"'"}],"timestamp":"'"$TIMESTAMP"'","footer":{"text":"Travis CI"}}]}' $DISCORD_WEBHOOK_URL; +curl -v -H User-Agent:bot -H Content-Type:application/json -d '{"avatar_url":"https://i.imgur.com/kOfUGNS.png","username":"Travis CI","embeds":[{"author":{"name":"Build #'"$TRAVIS_BUILD_NUMBER"' Failed '"$PYTHON_VERSION"' - '"$AUTHOR_NAME"'","url":"https://travis-ci.org/'"$REPO_OWNER"'/'"$REPO_NAME"'/builds/'"$TRAVIS_BUILD_ID"'"},"url":"https://github.com/'"$REPO_OWNER"'/'"$REPO_NAME"'/commit/'"$TRAVIS_COMMIT"'","title":"['"$TRAVIS_REPO_SLUG"':'"$TRAVIS_BRANCH"'] ","color":16711680,"fields":[{"name":"_ _", "value": "'"$COMMIT_FORMATTED"' - '"$TRAVIS_COMMIT_MESSAGE"'"}],"timestamp":"'"$TIMESTAMP"'","footer":{"text":"Travis CI"}}]}' $DISCORD_WEBHOOK_URL; diff --git a/success.sh b/success.sh index 1a18871..f3594ba 100644 --- a/success.sh +++ b/success.sh @@ -3,4 +3,4 @@ echo 'Sending Discord Webhook'; export BACKTICK='`'; export TIMESTAMP=$(date --utc +%FT%TZ); export COMMIT_FORMATTED="[$BACKTICK${TRAVIS_COMMIT:0:7}$BACKTICK](https://github.com/$REPO_OWNER/$REPO_NAME/commit/$TRAVIS_COMMIT)"; -curl -v -H User-Agent:bot -H Content-Type:application/json -d '{"avatar_url":"https://i.imgur.com/kOfUGNS.png","username":"Travis CI","embeds":[{"author":{"name":"Build #'"$TRAVIS_BUILD_NUMBER"' Passed - '"$AUTHOR_NAME"'","url":"https://travis-ci.org/'"$REPO_OWNER"'/'"$REPO_NAME"'/builds/'"$TRAVIS_BUILD_ID"'"},"url":"https://github.com/'"$REPO_OWNER"'/'"$REPO_NAME"'/commit/'"$TRAVIS_COMMIT"'","title":"['"$TRAVIS_REPO_SLUG"':'"$TRAVIS_BRANCH"'] ","color":65280,"fields":[{"name":"_ _", "value": "'"$COMMIT_FORMATTED"' - '"$TRAVIS_COMMIT_MESSAGE"'"}],"timestamp":"'"$TIMESTAMP"'","footer":{"text":"Travis CI"}}]}' $DISCORD_WEBHOOK_URL; +curl -v -H User-Agent:bot -H Content-Type:application/json -d '{"avatar_url":"https://i.imgur.com/kOfUGNS.png","username":"Travis CI","embeds":[{"author":{"name":"Build #'"$TRAVIS_BUILD_NUMBER"' Passed '"$PYTHON_VERSION"' - '"$AUTHOR_NAME"'","url":"https://travis-ci.org/'"$REPO_OWNER"'/'"$REPO_NAME"'/builds/'"$TRAVIS_BUILD_ID"'"},"url":"https://github.com/'"$REPO_OWNER"'/'"$REPO_NAME"'/commit/'"$TRAVIS_COMMIT"'","title":"['"$TRAVIS_REPO_SLUG"':'"$TRAVIS_BRANCH"'] ","color":65280,"fields":[{"name":"_ _", "value": "'"$COMMIT_FORMATTED"' - '"$TRAVIS_COMMIT_MESSAGE"'"}],"timestamp":"'"$TIMESTAMP"'","footer":{"text":"Travis CI"}}]}' $DISCORD_WEBHOOK_URL; From 9b8073f2ca8282a3edec4a67ba22d83f937adcbb Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 00:41:10 +0100 Subject: [PATCH 20/65] add more tests, one for local dev with simulationcraft, too, streamline simulation_data.simulate() --- .../simulation_objects/simulation_objects.py | 67 ++++++++----------- .../simulation_objects_tests.py | 29 ++++++++ 2 files changed, 58 insertions(+), 38 deletions(-) diff --git a/bloodytools/simulation_objects/simulation_objects.py b/bloodytools/simulation_objects/simulation_objects.py index 226f0e7..0597ecd 100644 --- a/bloodytools/simulation_objects/simulation_objects.py +++ b/bloodytools/simulation_objects/simulation_objects.py @@ -9,6 +9,8 @@ import uuid import logging +import subprocess + logger = logging.getLogger(__name__) class Error( Exception ): @@ -308,7 +310,7 @@ def set_full_report( self, report ): self.full_report = report return True else: - raise TypeError("Report as type {} found but string was expected.".format( type( report ) ) ) + raise TypeError("Report of type '{}'' found but string was expected.".format( type( report ) ) ) def set_simulation_end_time( self ): @@ -368,31 +370,20 @@ def simulate( self ): argument.append( "default_skill="+ self.default_skill ) argument.append( "ptr=" + self.ptr ) argument.append( "threads=" + self.threads ) - argument.append( "ready_trigger="+ self.ready_trigger ) + #argument.append( "ready_trigger="+ self.ready_trigger ) - for simc_argument in simc_arguments: + for simc_argument in self.simc_arguments: argument.append( simc_argument ) + fail_counter = 0 # should prevent additional empty windows popping up...on win32 systems without breaking different OS if sys.platform == 'win32': - # call simulationcraft in the background. grab output for processing and get dps value + # call simulationcraft in the background. Save output for processing startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - simulation_output = subprocess.run( - argument, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True, - startupinfo=startupinfo - ) - + while not hasattr(self, "success") and fail_counter < 5: - fail_counter = 0 - while simulation_output.returncode != 0 and fail_counter < 5: - logger.error( "ERROR: An Error occured during simulation." ) - logger.error( "args: " + str( simulation_output.args ) ) - logger.error( "stdout: " + str( simulation_output.stdout ) ) simulation_output = subprocess.run( argument, stdout=subprocess.PIPE, @@ -400,51 +391,51 @@ def simulate( self ): universal_newlines=True, startupinfo=startupinfo ) - fail_counter += 1 + + if simulation_output.returncode != 0: + fail_counter += 1 + else: + self.success = True else: - simulation_output = subprocess.run( - argument, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True - ) - - - fail_counter = 0 - while simulation_output.returncode != 0 and fail_counter < 5: - logger.error( "ERROR: An Error occured during simulation." ) - logger.error( "args: " + str( simulation_output.args ) ) - logger.error( "stdout: " + str( simulation_output.stdout ) ) + + while not hasattr(self, "success") and fail_counter < 5: + simulation_output = subprocess.run( argument, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True ) - fail_counter += 1 + + if simulation_output.returncode != 0: + fail_counter += 1 + else: + self.success = True if fail_counter >= 5: + logger.error( "ERROR: An Error occured during simulation." ) + logger.error( "args: " + str( simulation_output.args ) ) + logger.error( "stdout: " + str( simulation_output.stdout ) ) self.error = simulation_output.stdout raise SimulationError( self.error ) - # set simulation end time - self.set_simulation_end_time() # save output - self.set_full_report( simulation_output ) + self.set_full_report( simulation_output.stdout ) - actor_flag = True + is_actor = True run_dps = "DPS: 0.0" for line in simulation_output.stdout.splitlines(): # needs this check to prevent grabbing the boss dps - if "DPS:" in line and actor_flag: + if "DPS:" in line and is_actor: run_dps = line - actor_flag = False + is_actor = False dps_value = run_dps.split()[ 1 ] # save dps value self.set_dps( dps_value, external=False ) + self.set_simulation_end_time() return self.get_dps() diff --git a/bloodytools/simulation_objects/simulation_objects_tests.py b/bloodytools/simulation_objects/simulation_objects_tests.py index eccdb5b..79482b9 100644 --- a/bloodytools/simulation_objects/simulation_objects_tests.py +++ b/bloodytools/simulation_objects/simulation_objects_tests.py @@ -5,6 +5,7 @@ import unittest import uuid import time +import os import simulation_objects @@ -280,6 +281,34 @@ def test_set_full_report(self): self.assertTrue( self.sd.set_full_report( report ) ) self.assertEqual( self.sd.full_report, report ) + def test_set_simulation_end_time(self): + before = datetime.datetime.utcnow() + self.assertTrue( self.sd.set_simulation_end_time() ) + after = datetime.datetime.utcnow() + self.assertTrue( self.sd.so_simulation_end_time >= before ) + self.assertTrue( self.sd.so_simulation_end_time <= after ) + self.assertEqual( type( self.sd.so_simulation_end_time ), datetime.datetime ) + with self.assertRaises( simulation_objects.AlreadySetError ): + self.sd.set_simulation_end_time() + + def test_set_simulation_start_time(self): + self.assertEqual( self.sd.so_simulation_start_time, None ) + before = datetime.datetime.utcnow() + self.assertTrue( self.sd.set_simulation_start_time() ) + after = datetime.datetime.utcnow() + self.assertTrue( self.sd.so_simulation_start_time >= before ) + self.assertTrue( self.sd.so_simulation_start_time <= after ) + self.assertEqual( type( self.sd.so_simulation_start_time ), datetime.datetime ) + self.assertTrue( self.sd.set_simulation_start_time() ) + + # Does fail in travis. Currently intended as local test. Would need a working simc environement to actually run a test. + @unittest.skipUnless(os.path.isfile("D:\Programme\SimulationCraft\simc.exe"), "SimulationCraft wasn't found. This test is written for local dev for now. Don't worry.") + def test_simulate(self): + self.sd.executionable = "D:\Programme\SimulationCraft\simc.exe" + self.sd.target_error = "1.0" + self.sd.simc_arguments = [ "D:\Programme\SimulationCraft\profiles\Tier21\T21_Shaman_Elemental.simc" ] + self.assertEqual( type( self.sd.simulate() ), int ) + self.assertTrue( self.sd.get_dps() > 0 ) if __name__ == '__main__': From 043d800a63b126250a8a977b5f5e2c28b7a6eadd Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 09:01:09 +0100 Subject: [PATCH 21/65] first step to make simulationcraft clone work for tests --- .travis.yml | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 938632b..4c9531c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,20 +1,23 @@ -# This script is used by the Travis-CI (continuous integration) testing -# framework to run Bloodytools tests with every GitHub push or pull-request. -language: python +dist: trusty +sudo: false + +language: cpp + +compiler: + - gcc python: - 3.6 - - 3.5 - # can maybe added later when tests for simc-support are added for older versions - #- 3.4 - #- 3.3 - #- 2.7 branches: only: - dev install: + - git clone https://github.com/simulationcraft/simc.git Bloodmallet/SimulationCraft + - ls + - python3 -m venv env + - ./env/Scripts/activate - pip install -r ./requirements.txt - pip install -r ./requirements-test.txt From 73010104c7c7915e7db62590c4a06ba9af49b759 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 09:10:33 +0100 Subject: [PATCH 22/65] second try --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4c9531c..9670ad5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,8 +14,11 @@ branches: - dev install: - - git clone https://github.com/simulationcraft/simc.git Bloodmallet/SimulationCraft + - git clone https://github.com/simulationcraft/simc.git SimulationCraft - ls + - python --version + - python3 --version + - apt-get install python3-venv - python3 -m venv env - ./env/Scripts/activate - pip install -r ./requirements.txt From 24bc28db9c62623c7d4e366ea5aa8fb1d077c8fa Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 09:19:28 +0100 Subject: [PATCH 23/65] third take --- .travis.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9670ad5..41b74bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,15 +14,15 @@ branches: - dev install: - - git clone https://github.com/simulationcraft/simc.git SimulationCraft - ls - python --version - python3 --version - - apt-get install python3-venv - - python3 -m venv env - - ./env/Scripts/activate - - pip install -r ./requirements.txt - - pip install -r ./requirements-test.txt + - pip3 install -r ./requirements.txt + - pip3 install -r ./requirements-test.txt + - git clone https://github.com/simulationcraft/simc.git SimulationCraft + - cd SimulationCraft + - ./configure && make + - cd .. script: # Test simulation_objects and collect coverage information at the same time. From cd3f3824e5833bce10192c339d4a1399bfcb9afd Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 09:31:02 +0100 Subject: [PATCH 24/65] python env via addons? --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 41b74bc..e13eb24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,11 @@ branches: only: - dev +addons: + apt: + packages: + - python-virtualenv + install: - ls - python --version From 054e8efbda51fdd4bcdf56ed2acdd66875cc2a06 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 09:37:15 +0100 Subject: [PATCH 25/65] try to get pip instead --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e13eb24..a96d11e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,12 +16,14 @@ branches: addons: apt: packages: - - python-virtualenv + - python3-pip install: - ls - python --version - python3 --version + - pip --version + - pip3 --version - pip3 install -r ./requirements.txt - pip3 install -r ./requirements-test.txt - git clone https://github.com/simulationcraft/simc.git SimulationCraft From b7acbd733b875744367c0d78b8ce9fb293aa0127 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 09:43:16 +0100 Subject: [PATCH 26/65] install python3 venv, create env, activate, install... --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index a96d11e..1ad21c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,7 @@ addons: apt: packages: - python3-pip + - python3-venv install: - ls @@ -24,6 +25,8 @@ install: - python3 --version - pip --version - pip3 --version + - pyvenv env + - ./env/bin/activate - pip3 install -r ./requirements.txt - pip3 install -r ./requirements-test.txt - git clone https://github.com/simulationcraft/simc.git SimulationCraft From d9ca149151e2e86d023208b0bc77ca7d655a6882 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 09:49:14 +0100 Subject: [PATCH 27/65] no package named venv found --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1ad21c6..a9a519d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,6 @@ addons: apt: packages: - python3-pip - - python3-venv install: - ls @@ -25,8 +24,8 @@ install: - python3 --version - pip --version - pip3 --version - - pyvenv env - - ./env/bin/activate + - pyvenv-3.4 env + - source ./env/bin/activate - pip3 install -r ./requirements.txt - pip3 install -r ./requirements-test.txt - git clone https://github.com/simulationcraft/simc.git SimulationCraft From f0e9af9ef6c6a2d7a3765ebf3a89f6fc0ca0b9f5 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 09:52:24 +0100 Subject: [PATCH 28/65] add --user to pip --- .travis.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index a9a519d..a06ca65 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,10 +24,8 @@ install: - python3 --version - pip --version - pip3 --version - - pyvenv-3.4 env - - source ./env/bin/activate - - pip3 install -r ./requirements.txt - - pip3 install -r ./requirements-test.txt + - pip3 install --user -r ./requirements.txt + - pip3 install --user -r ./requirements-test.txt - git clone https://github.com/simulationcraft/simc.git SimulationCraft - cd SimulationCraft - ./configure && make From 154378dc87c6098377d4f6f1c9d52df0606bc968 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 10:01:50 +0100 Subject: [PATCH 29/65] try with only make --- .travis.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index a06ca65..446de26 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,16 +19,11 @@ addons: - python3-pip install: - - ls - - python --version - - python3 --version - - pip --version - - pip3 --version - pip3 install --user -r ./requirements.txt - pip3 install --user -r ./requirements-test.txt - git clone https://github.com/simulationcraft/simc.git SimulationCraft - cd SimulationCraft - - ./configure && make + - make - cd .. script: From 464a1eb7feb18bbe8321775678b0e19e0db52e27 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 10:16:32 +0100 Subject: [PATCH 30/65] add optimized --- .travis.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 446de26..30e41e3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,8 +22,12 @@ install: - pip3 install --user -r ./requirements.txt - pip3 install --user -r ./requirements-test.txt - git clone https://github.com/simulationcraft/simc.git SimulationCraft - - cd SimulationCraft - - make + - cd SimulationCraft/engine + - ls + - make OPENSSL=1 optimized + - ls + - cd .. + - ls - cd .. script: From 91b1d9aa34157a92a114ca01e504d7b1607cfbe0 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 10:36:32 +0100 Subject: [PATCH 31/65] fix simc test for travis --- .travis.yml | 10 ++++------ .../simulation_objects/simulation_objects_tests.py | 6 +++--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 30e41e3..4a6db77 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,16 +19,14 @@ addons: - python3-pip install: + - echo "Installing project requirements and test requirements." - pip3 install --user -r ./requirements.txt - pip3 install --user -r ./requirements-test.txt + - echo "Loading SimulationCraft and building its executable." - git clone https://github.com/simulationcraft/simc.git SimulationCraft - cd SimulationCraft/engine - - ls - - make OPENSSL=1 optimized - - ls - - cd .. - - ls - - cd .. + - make optimized + - cd ../.. script: # Test simulation_objects and collect coverage information at the same time. diff --git a/bloodytools/simulation_objects/simulation_objects_tests.py b/bloodytools/simulation_objects/simulation_objects_tests.py index 79482b9..5ff59d1 100644 --- a/bloodytools/simulation_objects/simulation_objects_tests.py +++ b/bloodytools/simulation_objects/simulation_objects_tests.py @@ -302,11 +302,11 @@ def test_set_simulation_start_time(self): self.assertTrue( self.sd.set_simulation_start_time() ) # Does fail in travis. Currently intended as local test. Would need a working simc environement to actually run a test. - @unittest.skipUnless(os.path.isfile("D:\Programme\SimulationCraft\simc.exe"), "SimulationCraft wasn't found. This test is written for local dev for now. Don't worry.") + @unittest.skipUnless(os.path.isfile("SimulationCraft\engine\simc"), "SimulationCraft wasn't found. This test is written for Travis.") def test_simulate(self): - self.sd.executionable = "D:\Programme\SimulationCraft\simc.exe" + self.sd.executionable = "SimulationCraft\engine\simc" self.sd.target_error = "1.0" - self.sd.simc_arguments = [ "D:\Programme\SimulationCraft\profiles\Tier21\T21_Shaman_Elemental.simc" ] + self.sd.simc_arguments = [ "SimulationCraft\profiles\Tier21\T21_Shaman_Elemental.simc" ] self.assertEqual( type( self.sd.simulate() ), int ) self.assertTrue( self.sd.get_dps() > 0 ) From d78e2dea3a078d7a798faba11b8c850e6109b41e Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 10:52:58 +0100 Subject: [PATCH 32/65] try to fix path to simulationcraft --- bloodytools/simulation_objects/simulation_objects_tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bloodytools/simulation_objects/simulation_objects_tests.py b/bloodytools/simulation_objects/simulation_objects_tests.py index 5ff59d1..1ef788d 100644 --- a/bloodytools/simulation_objects/simulation_objects_tests.py +++ b/bloodytools/simulation_objects/simulation_objects_tests.py @@ -302,11 +302,11 @@ def test_set_simulation_start_time(self): self.assertTrue( self.sd.set_simulation_start_time() ) # Does fail in travis. Currently intended as local test. Would need a working simc environement to actually run a test. - @unittest.skipUnless(os.path.isfile("SimulationCraft\engine\simc"), "SimulationCraft wasn't found. This test is written for Travis.") + @unittest.skipUnless(os.path.isfile("..\SimulationCraft\engine\simc"), "SimulationCraft wasn't found. This test is written for Travis.") def test_simulate(self): - self.sd.executionable = "SimulationCraft\engine\simc" + self.sd.executionable = "..\SimulationCraft\engine\simc" self.sd.target_error = "1.0" - self.sd.simc_arguments = [ "SimulationCraft\profiles\Tier21\T21_Shaman_Elemental.simc" ] + self.sd.simc_arguments = [ "..\SimulationCraft\profiles\Tier21\T21_Shaman_Elemental.simc" ] self.assertEqual( type( self.sd.simulate() ), int ) self.assertTrue( self.sd.get_dps() > 0 ) From 553930a56278eda7d0f81ad603469dd7c953d5fa Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 11:07:19 +0100 Subject: [PATCH 33/65] try to fix travis path 2.0 --- bloodytools/simulation_objects/simulation_objects_tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bloodytools/simulation_objects/simulation_objects_tests.py b/bloodytools/simulation_objects/simulation_objects_tests.py index 1ef788d..23d9227 100644 --- a/bloodytools/simulation_objects/simulation_objects_tests.py +++ b/bloodytools/simulation_objects/simulation_objects_tests.py @@ -302,11 +302,11 @@ def test_set_simulation_start_time(self): self.assertTrue( self.sd.set_simulation_start_time() ) # Does fail in travis. Currently intended as local test. Would need a working simc environement to actually run a test. - @unittest.skipUnless(os.path.isfile("..\SimulationCraft\engine\simc"), "SimulationCraft wasn't found. This test is written for Travis.") + @unittest.skipUnless(os.path.isfile("..\..\SimulationCraft\engine\simc"), "SimulationCraft wasn't found. This test is written for Travis.") def test_simulate(self): - self.sd.executionable = "..\SimulationCraft\engine\simc" + self.sd.executionable = "..\..\SimulationCraft\engine\simc" self.sd.target_error = "1.0" - self.sd.simc_arguments = [ "..\SimulationCraft\profiles\Tier21\T21_Shaman_Elemental.simc" ] + self.sd.simc_arguments = [ "..\..\SimulationCraft\profiles\Tier21\T21_Shaman_Elemental.simc" ] self.assertEqual( type( self.sd.simulate() ), int ) self.assertTrue( self.sd.get_dps() > 0 ) From f8faec231b73f9df77e79ebde05f48abeebdf292 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 11:26:02 +0100 Subject: [PATCH 34/65] where are we? --- bloodytools/simulation_objects/simulation_objects_tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bloodytools/simulation_objects/simulation_objects_tests.py b/bloodytools/simulation_objects/simulation_objects_tests.py index 23d9227..1d35478 100644 --- a/bloodytools/simulation_objects/simulation_objects_tests.py +++ b/bloodytools/simulation_objects/simulation_objects_tests.py @@ -302,8 +302,9 @@ def test_set_simulation_start_time(self): self.assertTrue( self.sd.set_simulation_start_time() ) # Does fail in travis. Currently intended as local test. Would need a working simc environement to actually run a test. - @unittest.skipUnless(os.path.isfile("..\..\SimulationCraft\engine\simc"), "SimulationCraft wasn't found. This test is written for Travis.") + #@unittest.skipUnless(os.path.isfile("..\..\SimulationCraft\engine\simc"), "SimulationCraft wasn't found. This test is written for Travis.") def test_simulate(self): + os.listdir() self.sd.executionable = "..\..\SimulationCraft\engine\simc" self.sd.target_error = "1.0" self.sd.simc_arguments = [ "..\..\SimulationCraft\profiles\Tier21\T21_Shaman_Elemental.simc" ] From c8e6496d0e3721352049001395a97f7be0f4efe1 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 11:59:59 +0100 Subject: [PATCH 35/65] print the dir content --- bloodytools/simulation_objects/simulation_objects_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bloodytools/simulation_objects/simulation_objects_tests.py b/bloodytools/simulation_objects/simulation_objects_tests.py index 1d35478..db3871d 100644 --- a/bloodytools/simulation_objects/simulation_objects_tests.py +++ b/bloodytools/simulation_objects/simulation_objects_tests.py @@ -304,7 +304,7 @@ def test_set_simulation_start_time(self): # Does fail in travis. Currently intended as local test. Would need a working simc environement to actually run a test. #@unittest.skipUnless(os.path.isfile("..\..\SimulationCraft\engine\simc"), "SimulationCraft wasn't found. This test is written for Travis.") def test_simulate(self): - os.listdir() + print(os.listdir()) self.sd.executionable = "..\..\SimulationCraft\engine\simc" self.sd.target_error = "1.0" self.sd.simc_arguments = [ "..\..\SimulationCraft\profiles\Tier21\T21_Shaman_Elemental.simc" ] From 2769eb540e03f3b71b66d4ab1aeddc1cc1137e9c Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 13:01:07 +0100 Subject: [PATCH 36/65] looking for more --- bloodytools/simulation_objects/simulation_objects_tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bloodytools/simulation_objects/simulation_objects_tests.py b/bloodytools/simulation_objects/simulation_objects_tests.py index db3871d..6c76f57 100644 --- a/bloodytools/simulation_objects/simulation_objects_tests.py +++ b/bloodytools/simulation_objects/simulation_objects_tests.py @@ -305,6 +305,7 @@ def test_set_simulation_start_time(self): #@unittest.skipUnless(os.path.isfile("..\..\SimulationCraft\engine\simc"), "SimulationCraft wasn't found. This test is written for Travis.") def test_simulate(self): print(os.listdir()) + print(os.listdir("SimulationCraft/engine")) self.sd.executionable = "..\..\SimulationCraft\engine\simc" self.sd.target_error = "1.0" self.sd.simc_arguments = [ "..\..\SimulationCraft\profiles\Tier21\T21_Shaman_Elemental.simc" ] From 6e42ba8cbea023018aedf774e38fca49c2734968 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 13:19:57 +0100 Subject: [PATCH 37/65] new old path --- .travis.yml | 1 + .../simulation_objects/simulation_objects_tests.py | 8 +++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4a6db77..adb4f8c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,6 +26,7 @@ install: - git clone https://github.com/simulationcraft/simc.git SimulationCraft - cd SimulationCraft/engine - make optimized + - chmod +x simc - cd ../.. script: diff --git a/bloodytools/simulation_objects/simulation_objects_tests.py b/bloodytools/simulation_objects/simulation_objects_tests.py index 6c76f57..0a1bef4 100644 --- a/bloodytools/simulation_objects/simulation_objects_tests.py +++ b/bloodytools/simulation_objects/simulation_objects_tests.py @@ -302,13 +302,11 @@ def test_set_simulation_start_time(self): self.assertTrue( self.sd.set_simulation_start_time() ) # Does fail in travis. Currently intended as local test. Would need a working simc environement to actually run a test. - #@unittest.skipUnless(os.path.isfile("..\..\SimulationCraft\engine\simc"), "SimulationCraft wasn't found. This test is written for Travis.") + @unittest.skipUnless(os.path.isfile("./SimulationCraft/engine/simc"), "SimulationCraft wasn't found. This test is written for Travis.") def test_simulate(self): - print(os.listdir()) - print(os.listdir("SimulationCraft/engine")) - self.sd.executionable = "..\..\SimulationCraft\engine\simc" + self.sd.executionable = "SimulationCraft\engine\simc" self.sd.target_error = "1.0" - self.sd.simc_arguments = [ "..\..\SimulationCraft\profiles\Tier21\T21_Shaman_Elemental.simc" ] + self.sd.simc_arguments = [ "SimulationCraft\profiles\Tier21\T21_Shaman_Elemental.simc" ] self.assertEqual( type( self.sd.simulate() ), int ) self.assertTrue( self.sd.get_dps() > 0 ) From a8b25dd2caaae8520a731219102aae88a8e23ccb Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 13:47:06 +0100 Subject: [PATCH 38/65] let's fail faster --- .travis.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index adb4f8c..95f15e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,11 +18,14 @@ addons: packages: - python3-pip +before_install: + - source ~/virtualenv/python3.6/bin/activate + install: - - echo "Installing project requirements and test requirements." + # Installing project requirements and test requirements. - pip3 install --user -r ./requirements.txt - pip3 install --user -r ./requirements-test.txt - - echo "Loading SimulationCraft and building its executable." + # Loading SimulationCraft and building its executable. - git clone https://github.com/simulationcraft/simc.git SimulationCraft - cd SimulationCraft/engine - make optimized From d84f4388d3c7fa35fd8429b580dc5275052fca0f Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 13:48:39 +0100 Subject: [PATCH 39/65] show python version --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 95f15e9..8dc1c44 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,7 @@ before_install: - source ~/virtualenv/python3.6/bin/activate install: + - python --version # Installing project requirements and test requirements. - pip3 install --user -r ./requirements.txt - pip3 install --user -r ./requirements-test.txt From e48c9f460c183380bd05a1b6d16293be9a2c6958 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 13:50:33 +0100 Subject: [PATCH 40/65] env is used, do i still need to install pip there? --- .travis.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8dc1c44..0a468c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,10 +13,10 @@ branches: only: - dev -addons: - apt: - packages: - - python3-pip +#addons: +# apt: +# packages: +# - python3-pip before_install: - source ~/virtualenv/python3.6/bin/activate @@ -24,8 +24,8 @@ before_install: install: - python --version # Installing project requirements and test requirements. - - pip3 install --user -r ./requirements.txt - - pip3 install --user -r ./requirements-test.txt + - pip install -r ./requirements.txt + - pip install -r ./requirements-test.txt # Loading SimulationCraft and building its executable. - git clone https://github.com/simulationcraft/simc.git SimulationCraft - cd SimulationCraft/engine From 68e4d6150eb2781070b93dbbdde0f14e80186487 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 14:03:19 +0100 Subject: [PATCH 41/65] change path slashs --- .travis.yml | 1 + bloodytools/simulation_objects/simulation_objects_tests.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0a468c5..6ecb807 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,7 @@ branches: # - python3-pip before_install: + # activate out of the box python 3.6 environement - source ~/virtualenv/python3.6/bin/activate install: diff --git a/bloodytools/simulation_objects/simulation_objects_tests.py b/bloodytools/simulation_objects/simulation_objects_tests.py index 0a1bef4..bb71148 100644 --- a/bloodytools/simulation_objects/simulation_objects_tests.py +++ b/bloodytools/simulation_objects/simulation_objects_tests.py @@ -304,9 +304,9 @@ def test_set_simulation_start_time(self): # Does fail in travis. Currently intended as local test. Would need a working simc environement to actually run a test. @unittest.skipUnless(os.path.isfile("./SimulationCraft/engine/simc"), "SimulationCraft wasn't found. This test is written for Travis.") def test_simulate(self): - self.sd.executionable = "SimulationCraft\engine\simc" + self.sd.executionable = "./SimulationCraft/engine/simc" self.sd.target_error = "1.0" - self.sd.simc_arguments = [ "SimulationCraft\profiles\Tier21\T21_Shaman_Elemental.simc" ] + self.sd.simc_arguments = [ "./SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" ] self.assertEqual( type( self.sd.simulate() ), int ) self.assertTrue( self.sd.get_dps() > 0 ) From f1fc45acd12b7221f689894228dd703d74138393 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 15:19:21 +0100 Subject: [PATCH 42/65] improve comments --- .travis.yml | 21 +++++++++------------ README.md | 2 +- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6ecb807..e9b949a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,49 +11,46 @@ python: branches: only: + # only trigger CI on dev branch - dev -#addons: -# apt: -# packages: -# - python3-pip - before_install: # activate out of the box python 3.6 environement - source ~/virtualenv/python3.6/bin/activate install: - python --version - # Installing project requirements and test requirements. + # Install project requirements and test requirements - pip install -r ./requirements.txt - pip install -r ./requirements-test.txt - # Loading SimulationCraft and building its executable. + # Load SimulationCraft - git clone https://github.com/simulationcraft/simc.git SimulationCraft - cd SimulationCraft/engine + # Build SimulationCraft executable - make optimized - chmod +x simc - cd ../.. script: - # Test simulation_objects and collect coverage information at the same time. + # Test simulation_objects and collect coverage information at the same time - coverage run --source=bloodytools/simulation_objects --omit=*_tests.py ./bloodytools/simulation_objects/simulation_objects_tests.py -# makes travis send a notification to discord with these values before_script: + # Save values for discord notifactions - export AUTHOR_NAME="$(git log -1 $TRAVIS_COMMIT --pretty="%aN")" - export PYTHON_VERSION="$(python --version)" -# makes travis send a notification to discord after_success: + # Send notification to discord - chmod +x success.sh - ./success.sh - coveralls -# makes travis send a notification to discord after_failure: + # Send notification to discord - chmod +x fail.sh - ./fail.sh -# no email notifications, this shall be handled/viewed via discord notifications: + # Deactivate email notifications email: false diff --git a/README.md b/README.md index 7befccd..65026aa 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Bloody( tools ) =========== [![Build Status](https://travis-ci.org/Bloodmallet/bloodytools.svg?branch=dev)](https://travis-ci.org/Bloodmallet/bloodytools) [![Coverage Status](https://coveralls.io/repos/github/Bloodmallet/bloodytools/badge.svg?branch=dev)](https://coveralls.io/github/Bloodmallet/bloodytools?branch=dev) -> Automation tool for several different aspects of all dps and some tank specs in World of Warcraft using SimulationCraft. +> Automation tool for several different aspects of all dps and some tank specs in World of Warcraft using SimulationCraft. Ingame customization and number tuning make decision making without external help a bloody hell. ## Requirements You need the latest [SimulationCraft](http://downloads.simulationcraft.org/?C=M;O=D) version ([GitHub Repository](https://github.com/simulationcraft/simc)), [Python 3.5](https://www.python.org/downloads/) or newer and the module [simc_support](https://github.com/Bloodmallet/simc_support), which is handled in the requirements.txt. From e7deae58565989e47420d09994e6149491f05d58 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 5 Feb 2018 17:19:25 +0100 Subject: [PATCH 43/65] add more tests, streamline functions --- .../simulation_objects/simulation_objects.py | 102 ++++++----- .../simulation_objects_tests.py | 162 +++++++++++++++++- 2 files changed, 213 insertions(+), 51 deletions(-) diff --git a/bloodytools/simulation_objects/simulation_objects.py b/bloodytools/simulation_objects/simulation_objects.py index 0597ecd..583555e 100644 --- a/bloodytools/simulation_objects/simulation_objects.py +++ b/bloodytools/simulation_objects/simulation_objects.py @@ -41,6 +41,8 @@ class SimulationError( Error ): class simulation_data(): """ Manages all META-information for a simulation and the result. + + TODO: add max_time, vary_combat_length """ def __init__( self, calculate_scale_factors="0", @@ -218,7 +220,7 @@ def is_equal(self, simulation_instance): return False return True except Exception as e: - False + return False def get_dps( self ): @@ -323,12 +325,8 @@ def set_simulation_end_time( self ): @return True, if set. """ if not self.so_simulation_end_time: - try: - self.so_simulation_end_time = datetime.datetime.utcnow() - except Exception as e: - raise e - else: - return True + self.so_simulation_end_time = datetime.datetime.utcnow() + return True else: raise AlreadySetError( "Simulation end time was already set. Setting it twice is not allowed." ) @@ -342,12 +340,8 @@ def set_simulation_start_time( self ): @return True, if set. """ - try: - self.so_simulation_start_time = datetime.datetime.utcnow() - except Exception as e: - raise e - else: - return True + self.so_simulation_start_time = datetime.datetime.utcnow() + return True def simulate( self ): @@ -384,13 +378,16 @@ def simulate( self ): while not hasattr(self, "success") and fail_counter < 5: - simulation_output = subprocess.run( - argument, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True, - startupinfo=startupinfo - ) + try: + simulation_output = subprocess.run( + argument, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + startupinfo=startupinfo + ) + except FileNotFoundError as e: + raise e if simulation_output.returncode != 0: fail_counter += 1 @@ -401,12 +398,15 @@ def simulate( self ): while not hasattr(self, "success") and fail_counter < 5: - simulation_output = subprocess.run( - argument, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True - ) + try: + simulation_output = subprocess.run( + argument, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True + ) + except FileNotFoundError as e: + raise e if simulation_output.returncode != 0: fail_counter += 1 @@ -448,18 +448,24 @@ class simulation_group(): the simulation_data. """ def __init__( self, simulation_instance, name="" ): - super( simulation_group, self ).__init__() + self.name = name + if type( simulation_instance ) == list: - self.profiles = simulation_instance - elif type( simulation_instance ) == simulation_instance: + correct_type = True + for data in simulation_instance: + if type(data) != simulation_data: + correct_type = False + if correct_type: + self.profiles = simulation_instance + else: + raise TypeError( "At least one item of simulation_instance list had a wrong type. Expected simulation_data." ) + elif type( simulation_instance ) == simulation_data: self.profiles = [ simulation_instance ] else: - raise TypeError( "Simulation_instance has wrong type '{}' (needed list or single simulation_data).".format(type(simulation_instance)) ) - # optional name of the simulation_group - self.name = name + raise TypeError( "Simulation_instance has wrong type '{}'. Expected list or single simulation_data.".format(type(simulation_instance)) ) - if not selfcheck(): - raise InputError( "simulation_instance base data wasn't coherent." ) + if not self.selfcheck(): + raise InputError( "At least one item of simulation_instance had data that didn't match the others." ) def selfcheck( self ): @@ -488,16 +494,26 @@ def simulate( self ): @return True on success. """ - if length( self.profiles ) == 1: - # if only one profiles is in the group this profile is simulated normally - self.profiles[ 0 ].simulate() + if self.profiles: + if len( self.profiles ) == 1: + # if only one profiles is in the group this profile is simulated normally + try: + self.profiles[ 0 ].simulate() + except Exception as e: + raise e + elif len( self.profiles ) >= 2: + # profile set creation based on profiles content + # create file with profile set content + # start simulation with profilesets + # grab data + # push data into simulation_data.set_dps(dps) + pass + else: + return False else: - # profile set creation based on profiles content - # create file with profile set content - # start simulation with profilesets - # grab data - # push data into simulation_data.set_dps(dps) - pass + return False + + return True diff --git a/bloodytools/simulation_objects/simulation_objects_tests.py b/bloodytools/simulation_objects/simulation_objects_tests.py index bb71148..3dde53a 100644 --- a/bloodytools/simulation_objects/simulation_objects_tests.py +++ b/bloodytools/simulation_objects/simulation_objects_tests.py @@ -232,9 +232,35 @@ def test_is_equal(self): self.assertTrue( sd1.is_equal( sd2 ) ) self.assertTrue( sd2.is_equal( sd1 ) ) self.assertTrue( sd1.is_equal( sd1 ) ) - sd3 = simulation_objects.simulation_data( iterations="124153", threads="8" ) - self.assertFalse( sd3.is_equal( sd1 ) ) - self.assertFalse( sd3.is_equal( "Döner" ) ) + self.assertFalse( sd1.is_equal( "Döner" ) ) + sd_calculate_scale_factors = simulation_objects.simulation_data( calculate_scale_factors="1" ) + self.assertFalse( sd_calculate_scale_factors.is_equal( sd1 ) ) + sd_default_actions = simulation_objects.simulation_data( default_actions="0" ) + self.assertFalse( sd_default_actions.is_equal( sd1 ) ) + sd_default_skill = simulation_objects.simulation_data( default_skill="0.5" ) + self.assertFalse( sd_default_skill.is_equal( sd1 ) ) + sd_executionable = simulation_objects.simulation_data( executionable="1" ) + self.assertFalse( sd_executionable.is_equal( sd1 ) ) + sd_fight_style = simulation_objects.simulation_data( fight_style="helterskelter" ) + self.assertFalse( sd_fight_style.is_equal( sd1 ) ) + sd_fixed_time = simulation_objects.simulation_data( fixed_time="0" ) + self.assertFalse( sd_fixed_time.is_equal( sd1 ) ) + sd_html = simulation_objects.simulation_data( html="test.html" ) + self.assertFalse( sd_html.is_equal( sd1 ) ) + sd_iterations = simulation_objects.simulation_data( iterations="124153" ) + self.assertFalse( sd_iterations.is_equal( sd1 ) ) + sd_log = simulation_objects.simulation_data( log="1" ) + self.assertFalse( sd_log.is_equal( sd1 ) ) + sd_optimize_expressions = simulation_objects.simulation_data( optimize_expressions="0" ) + self.assertFalse( sd_optimize_expressions.is_equal( sd1 ) ) + sd_ptr = simulation_objects.simulation_data( ptr="1" ) + self.assertFalse( sd_ptr.is_equal( sd1 ) ) + sd_ready_trigger = simulation_objects.simulation_data( ready_trigger="0" ) + self.assertFalse( sd_ready_trigger.is_equal( sd1 ) ) + sd_target_error = simulation_objects.simulation_data( target_error="0.5" ) + self.assertFalse( sd_target_error.is_equal( sd1 ) ) + sd_threads = simulation_objects.simulation_data( threads="4" ) + self.assertFalse( sd_threads.is_equal( sd1 ) ) def test_get_dps(self): self.assertEqual(self.sd.get_dps(), None) @@ -248,7 +274,6 @@ def test_set_dps(self): self.sd.set_dps( 5678.9 ) with self.assertRaises( TypeError ): self.sd.set_dps( "5678", external="Hallo" ) - self.sd = None self.sd = simulation_objects.simulation_data() with self.assertRaises( TypeError ): @@ -301,15 +326,136 @@ def test_set_simulation_start_time(self): self.assertEqual( type( self.sd.so_simulation_start_time ), datetime.datetime ) self.assertTrue( self.sd.set_simulation_start_time() ) - # Does fail in travis. Currently intended as local test. Would need a working simc environement to actually run a test. - @unittest.skipUnless(os.path.isfile("./SimulationCraft/engine/simc"), "SimulationCraft wasn't found. This test is written for Travis.") def test_simulate(self): - self.sd.executionable = "./SimulationCraft/engine/simc" + # travis-ci test + # bloodytools/ + # ./ + # bloodytools/ + # simulation_objects/ + # SimulationCraft/ + # engine/ + if os.path.isfile("./SimulationCraft/engine/simc"): + self.sd.executionable = "./SimulationCraft/engine/simc" + self.sd.simc_arguments = [ "./SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" ] + + # local dev test + # SimulationCraft/ + # bloodytools/ + # ./ + # bloodytools/ + # simulation_objects/ + elif os.path.isfile("../SimulationCraft/simc.exe"): + self.sd.executionable = "../SimulationCraft/simc.exe" + self.sd.simc_arguments = [ "../SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" ] + + # use standard values + # SimulationCraft/ + # bloodytools/ + # ./ + # bloodytools/ + # bloodytools/ + # simulation_objects/ + else: + pass self.sd.target_error = "1.0" - self.sd.simc_arguments = [ "./SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" ] self.assertEqual( type( self.sd.simulate() ), int ) self.assertTrue( self.sd.get_dps() > 0 ) + self.assertEqual( type( self.sd.so_simulation_end_time ), datetime.datetime ) + + def test_simulate_fail(self): + self.sd.executionable = "Not_a_correct_value" + with self.assertRaises( FileNotFoundError ): + self.sd.simulate() + +## +## @brief Test handling of init values +## +class TestSimulationGroupDataInit(unittest.TestCase): + def setUp(self): + self.sd1 = simulation_objects.simulation_data() + self.sd2 = simulation_objects.simulation_data() + + def tearDown(self): + self.sd1 = None + self.sd2 = None + + def test_empty(self): + with self.assertRaises(TypeError): + test = simulation_objects.simulation_group() + def test_correct_list_input(self): + test = simulation_objects.simulation_group( [ self.sd1, self.sd2 ] ) + self.assertEqual( test.profiles[0], self.sd1 ) + self.assertEqual( test.profiles[1], self.sd2 ) + + def test_correct_single_input(self): + test = simulation_objects.simulation_group( self.sd1 ) + self.assertEqual( test.profiles[0], self.sd1 ) + + def test_wrong_list_input(self): + tmp = simulation_objects.simulation_data( iterations="5000" ) + with self.assertRaises(simulation_objects.InputError): + test = simulation_objects.simulation_group( [ self.sd1, tmp ] ) + with self.assertRaises(TypeError): + test = simulation_objects.simulation_group( [ "why", "would", "anyone", "do", "this" ] ) + + def test_wrong_input_type(self): + with self.assertRaises(TypeError): + test = simulation_objects.simulation_group( ( self.sd1, self.sd2 ) ) + + +## +## @brief Test methods of simulation_group +## +class TestSimulationGroupMethods(unittest.TestCase): + def setUp(self): + self.sd1 = simulation_objects.simulation_data( target_error="1.0" ) + self.sd2 = simulation_objects.simulation_data( target_error=1.0 ) + self.sg = simulation_objects.simulation_group( [ self.sd1, self.sd2 ] ) + + def test_selfcheck(self): + self.assertTrue( self.sg.selfcheck() ) + self.sg.profiles[1].calculate_scale_factors = "1" + self.assertFalse( self.sg.selfcheck() ) + + def test_add(self): + start = len( self.sg.profiles ) + new_data = simulation_objects.simulation_data( target_error=1.0) + self.assertTrue( self.sg.add( new_data ) ) + self.assertEqual( len( self.sg.profiles ), start + 1 ) + with self.assertRaises(TypeError): + self.sg.add( "Bananana" ) + + def test_simulate(self): + self.sd1.executionable = "Not_a_correct_value" + sole_sg1 = simulation_objects.simulation_group( self.sd1 ) + with self.assertRaises( FileNotFoundError ): + sole_sg1.simulate() + + # travis-ci test + # bloodytools/ + # ./ + # bloodytools/ + # simulation_objects/ + # SimulationCraft/ + # engine/ + if os.path.isfile("./SimulationCraft/engine/simc"): + self.sd2.executionable = "./SimulationCraft/engine/simc" + self.sd2.simc_arguments = [ "./SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" ] + + # local dev test + # SimulationCraft/ + # bloodytools/ + # ./ + # bloodytools/ + # simulation_objects/ + elif os.path.isfile("../SimulationCraft/simc.exe"): + self.sd2.executionable = "../SimulationCraft/simc.exe" + self.sd2.simc_arguments = [ "../SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" ] + sole_sg2 = simulation_objects.simulation_group( self.sd2 ) + self.assertTrue( sole_sg2.simulate() ) + self.sg.profiles = None + self.assertFalse( self.sg.simulate() ) if __name__ == '__main__': unittest.main() From e83044f698da2357fc8e6bc783686f81663d629d Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Wed, 21 Feb 2018 16:20:52 +0100 Subject: [PATCH 44/65] try out visual studio code to develope bloodytools change baseline formatter delete unsued variables add vs code directory to gitignore --- .gitignore | 9 +- .../simulation_objects_tests.py | 465 +++++++++--------- 2 files changed, 252 insertions(+), 222 deletions(-) diff --git a/.gitignore b/.gitignore index c9f8242..96e7d14 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,10 @@ -env35_pyqt -build-versuch_zwei-Desktop_Qt_5_8_0_MSVC2015_64bit-Debug +# locald ev env /env + *.pyc + +# local coverage results .coverage + +# local editor +/.vscode \ No newline at end of file diff --git a/bloodytools/simulation_objects/simulation_objects_tests.py b/bloodytools/simulation_objects/simulation_objects_tests.py index 3dde53a..661b3ac 100644 --- a/bloodytools/simulation_objects/simulation_objects_tests.py +++ b/bloodytools/simulation_objects/simulation_objects_tests.py @@ -11,6 +11,7 @@ logger = logging.getLogger(__name__) + ## ## @brief Simple tests for the handling of init values for objects of the ## simulation_data class. @@ -37,26 +38,26 @@ def tearDown(self): ## def test_empty(self): self.sd = simulation_objects.simulation_data() - self.assertEqual( self.sd.calculate_scale_factors, "0" ) - self.assertEqual( self.sd.default_actions, "1" ) - self.assertEqual( self.sd.default_skill, "1.0" ) - self.assertEqual( self.sd.fight_style, "patchwerk" ) - self.assertEqual( self.sd.fixed_time, "1" ) - self.assertEqual( self.sd.html, "" ) - self.assertEqual( self.sd.iterations, "250000" ) - self.assertEqual( self.sd.log, "0" ) - self.assertNotEqual( self.sd.name, None ) - self.assertEqual( self.sd.optimize_expressions, "1" ) - self.assertEqual( self.sd.ptr, "0" ) - self.assertEqual( self.sd.ready_trigger, "1" ) - self.assertEqual( self.sd.simc_arguments, [] ) - self.assertEqual( self.sd.target_error, "0.1" ) - self.assertEqual( self.sd.threads, "" ) - self.assertEqual( type( self.sd.so_creation_time), datetime.datetime ) - self.assertEqual( self.sd.external_simulation, False ) - self.assertEqual( self.sd.full_report, None ) - self.assertEqual( self.sd.so_simulation_end_time, None ) - self.assertEqual( self.sd.so_simulation_start_time, None ) + self.assertEqual(self.sd.calculate_scale_factors, "0") + self.assertEqual(self.sd.default_actions, "1") + self.assertEqual(self.sd.default_skill, "1.0") + self.assertEqual(self.sd.fight_style, "patchwerk") + self.assertEqual(self.sd.fixed_time, "1") + self.assertEqual(self.sd.html, "") + self.assertEqual(self.sd.iterations, "250000") + self.assertEqual(self.sd.log, "0") + self.assertNotEqual(self.sd.name, None) + self.assertEqual(self.sd.optimize_expressions, "1") + self.assertEqual(self.sd.ptr, "0") + self.assertEqual(self.sd.ready_trigger, "1") + self.assertEqual(self.sd.simc_arguments, []) + self.assertEqual(self.sd.target_error, "0.1") + self.assertEqual(self.sd.threads, "") + self.assertEqual(type(self.sd.so_creation_time), datetime.datetime) + self.assertEqual(self.sd.external_simulation, False) + self.assertEqual(self.sd.full_report, None) + self.assertEqual(self.sd.so_simulation_end_time, None) + self.assertEqual(self.sd.so_simulation_start_time, None) ## ## @brief Test input checks of calculate_scale_factors. @@ -66,13 +67,13 @@ def test_empty(self): ## @return { description_of_the_return_value } ## def test_calculate_scale_factors(self): - self.sd = simulation_objects.simulation_data( calculate_scale_factors="1" ) - self.assertEqual( self.sd.calculate_scale_factors, "1" ) - self.assertNotEqual( self.sd.calculate_scale_factors, "0" ) - self.assertNotEqual( self.sd.calculate_scale_factors, str ) - self.assertNotEqual( self.sd.calculate_scale_factors, list ) - self.assertNotEqual( self.sd.calculate_scale_factors, dict ) - self.assertNotEqual( self.sd.calculate_scale_factors, int ) + self.sd = simulation_objects.simulation_data(calculate_scale_factors="1") + self.assertEqual(self.sd.calculate_scale_factors, "1") + self.assertNotEqual(self.sd.calculate_scale_factors, "0") + self.assertNotEqual(self.sd.calculate_scale_factors, str) + self.assertNotEqual(self.sd.calculate_scale_factors, list) + self.assertNotEqual(self.sd.calculate_scale_factors, dict) + self.assertNotEqual(self.sd.calculate_scale_factors, int) ## ## @brief Test input checks of default_actions. @@ -82,144 +83,146 @@ def test_calculate_scale_factors(self): ## @return { description_of_the_return_value } ## def test_default_actions(self): - self.sd = simulation_objects.simulation_data( default_actions="1" ) - self.assertEqual( self.sd.default_actions, "1" ) - self.assertNotEqual( self.sd.default_actions, "0" ) - self.assertNotEqual( self.sd.default_actions, str ) - self.assertNotEqual( self.sd.default_actions, list ) - self.assertNotEqual( self.sd.default_actions, dict ) - self.assertNotEqual( self.sd.default_actions, int ) + self.sd = simulation_objects.simulation_data(default_actions="1") + self.assertEqual(self.sd.default_actions, "1") + self.assertNotEqual(self.sd.default_actions, "0") + self.assertNotEqual(self.sd.default_actions, str) + self.assertNotEqual(self.sd.default_actions, list) + self.assertNotEqual(self.sd.default_actions, dict) + self.assertNotEqual(self.sd.default_actions, int) def test_fight_style(self): - self.sd = simulation_objects.simulation_data( fight_style="helterskelter" ) - self.assertEqual( self.sd.fight_style, "helterskelter" ) - self.assertNotEqual( self.sd.fight_style, "0" ) - self.assertNotEqual( self.sd.fight_style, str ) - self.assertNotEqual( self.sd.fight_style, list ) - self.assertNotEqual( self.sd.fight_style, dict ) - self.assertNotEqual( self.sd.fight_style, int ) + self.sd = simulation_objects.simulation_data(fight_style="helterskelter") + self.assertEqual(self.sd.fight_style, "helterskelter") + self.assertNotEqual(self.sd.fight_style, "0") + self.assertNotEqual(self.sd.fight_style, str) + self.assertNotEqual(self.sd.fight_style, list) + self.assertNotEqual(self.sd.fight_style, dict) + self.assertNotEqual(self.sd.fight_style, int) # simc_checks needs improvements to catch this #self.sd = None #self.sd = simulation_objects.simulation_data( fight_style=1234 ) #self.assertEqual( self.sd.fight_style, "patchwerk" ) def test_fixed_time(self): - self.sd = simulation_objects.simulation_data( fixed_time="1" ) - self.assertEqual( self.sd.fixed_time, "1" ) - self.assertNotEqual( self.sd.fixed_time, "0" ) - self.assertNotEqual( self.sd.fixed_time, str ) - self.assertNotEqual( self.sd.fixed_time, list ) - self.assertNotEqual( self.sd.fixed_time, dict ) - self.assertNotEqual( self.sd.fixed_time, int ) + self.sd = simulation_objects.simulation_data(fixed_time="1") + self.assertEqual(self.sd.fixed_time, "1") + self.assertNotEqual(self.sd.fixed_time, "0") + self.assertNotEqual(self.sd.fixed_time, str) + self.assertNotEqual(self.sd.fixed_time, list) + self.assertNotEqual(self.sd.fixed_time, dict) + self.assertNotEqual(self.sd.fixed_time, int) def test_html(self): - self.sd = simulation_objects.simulation_data( html="testiger.html" ) - self.assertEqual( self.sd.html, "testiger.html" ) - self.assertNotEqual( self.sd.html, "" ) - self.assertNotEqual( self.sd.html, str ) - self.assertNotEqual( self.sd.html, list ) - self.assertNotEqual( self.sd.html, dict ) - self.assertNotEqual( self.sd.html, int ) + self.sd = simulation_objects.simulation_data(html="testiger.html") + self.assertEqual(self.sd.html, "testiger.html") + self.assertNotEqual(self.sd.html, "") + self.assertNotEqual(self.sd.html, str) + self.assertNotEqual(self.sd.html, list) + self.assertNotEqual(self.sd.html, dict) + self.assertNotEqual(self.sd.html, int) def test_iterations(self): - self.sd = simulation_objects.simulation_data( iterations="15000" ) - self.assertEqual( self.sd.iterations, "15000" ) - self.assertNotEqual( self.sd.iterations, "" ) - self.assertNotEqual( self.sd.iterations, str ) - self.assertNotEqual( self.sd.iterations, list ) - self.assertNotEqual( self.sd.iterations, dict ) - self.assertNotEqual( self.sd.iterations, int ) + self.sd = simulation_objects.simulation_data(iterations="15000") + self.assertEqual(self.sd.iterations, "15000") + self.assertNotEqual(self.sd.iterations, "") + self.assertNotEqual(self.sd.iterations, str) + self.assertNotEqual(self.sd.iterations, list) + self.assertNotEqual(self.sd.iterations, dict) + self.assertNotEqual(self.sd.iterations, int) self.sd = None - self.sd = simulation_objects.simulation_data( iterations=15000 ) - self.assertEqual( self.sd.iterations, "15000" ) + self.sd = simulation_objects.simulation_data(iterations=15000) + self.assertEqual(self.sd.iterations, "15000") self.sd = None - self.sd = simulation_objects.simulation_data( iterations=15000.75 ) - self.assertEqual( self.sd.iterations, "15000" ) + self.sd = simulation_objects.simulation_data(iterations=15000.75) + self.assertEqual(self.sd.iterations, "15000") def test_log(self): - self.sd = simulation_objects.simulation_data( log="1" ) - self.assertEqual( self.sd.log, "1" ) - self.assertNotEqual( self.sd.log, "" ) - self.assertNotEqual( self.sd.log, str ) - self.assertNotEqual( self.sd.log, list ) - self.assertNotEqual( self.sd.log, dict ) - self.assertNotEqual( self.sd.log, int ) + self.sd = simulation_objects.simulation_data(log="1") + self.assertEqual(self.sd.log, "1") + self.assertNotEqual(self.sd.log, "") + self.assertNotEqual(self.sd.log, str) + self.assertNotEqual(self.sd.log, list) + self.assertNotEqual(self.sd.log, dict) + self.assertNotEqual(self.sd.log, int) self.sd = None - self.sd = simulation_objects.simulation_data( iterations=15000 ) - self.assertEqual( self.sd.log, "0" ) + self.sd = simulation_objects.simulation_data(iterations=15000) + self.assertEqual(self.sd.log, "0") def test_name(self): - self.sd = simulation_objects.simulation_data( name="" ) - self.assertNotEqual( self.sd.name, "" ) - self.assertEqual( type( self.sd.name ), uuid.UUID ) + self.sd = simulation_objects.simulation_data(name="") + self.assertNotEqual(self.sd.name, "") + self.assertEqual(type(self.sd.name), uuid.UUID) self.sd = None - self.sd = simulation_objects.simulation_data( name="Borke" ) - self.assertEqual( self.sd.name, "Borke" ) + self.sd = simulation_objects.simulation_data(name="Borke") + self.assertEqual(self.sd.name, "Borke") def test_optimize_expressions(self): - self.sd = simulation_objects.simulation_data( optimize_expressions="1" ) - self.assertEqual( self.sd.optimize_expressions, "1" ) - self.assertNotEqual( self.sd.optimize_expressions, "0" ) - self.assertNotEqual( self.sd.optimize_expressions, str ) - self.assertNotEqual( self.sd.optimize_expressions, list ) - self.assertNotEqual( self.sd.optimize_expressions, dict ) - self.assertNotEqual( self.sd.optimize_expressions, int ) + self.sd = simulation_objects.simulation_data(optimize_expressions="1") + self.assertEqual(self.sd.optimize_expressions, "1") + self.assertNotEqual(self.sd.optimize_expressions, "0") + self.assertNotEqual(self.sd.optimize_expressions, str) + self.assertNotEqual(self.sd.optimize_expressions, list) + self.assertNotEqual(self.sd.optimize_expressions, dict) + self.assertNotEqual(self.sd.optimize_expressions, int) self.sd = None - self.sd = simulation_objects.simulation_data( optimize_expressions=["1"] ) - self.assertEqual( self.sd.optimize_expressions, "1" ) + self.sd = simulation_objects.simulation_data(optimize_expressions=["1"]) + self.assertEqual(self.sd.optimize_expressions, "1") def test_ptr(self): - self.sd = simulation_objects.simulation_data( ptr="1" ) - self.assertEqual( self.sd.ptr, "1" ) - self.assertNotEqual( self.sd.ptr, "0" ) - self.assertNotEqual( self.sd.ptr, str ) - self.assertNotEqual( self.sd.ptr, list ) - self.assertNotEqual( self.sd.ptr, dict ) - self.assertNotEqual( self.sd.ptr, int ) + self.sd = simulation_objects.simulation_data(ptr="1") + self.assertEqual(self.sd.ptr, "1") + self.assertNotEqual(self.sd.ptr, "0") + self.assertNotEqual(self.sd.ptr, str) + self.assertNotEqual(self.sd.ptr, list) + self.assertNotEqual(self.sd.ptr, dict) + self.assertNotEqual(self.sd.ptr, int) self.sd = None - self.sd = simulation_objects.simulation_data( ptr=["1"] ) - self.assertEqual( self.sd.ptr, "0" ) + self.sd = simulation_objects.simulation_data(ptr=["1"]) + self.assertEqual(self.sd.ptr, "0") def test_ready_trigger(self): - self.sd = simulation_objects.simulation_data( ready_trigger="1" ) - self.assertEqual( self.sd.ready_trigger, "1" ) - self.assertNotEqual( self.sd.ready_trigger, "0" ) - self.assertNotEqual( self.sd.ready_trigger, str ) - self.assertNotEqual( self.sd.ready_trigger, list ) - self.assertNotEqual( self.sd.ready_trigger, dict ) - self.assertNotEqual( self.sd.ready_trigger, int ) + self.sd = simulation_objects.simulation_data(ready_trigger="1") + self.assertEqual(self.sd.ready_trigger, "1") + self.assertNotEqual(self.sd.ready_trigger, "0") + self.assertNotEqual(self.sd.ready_trigger, str) + self.assertNotEqual(self.sd.ready_trigger, list) + self.assertNotEqual(self.sd.ready_trigger, dict) + self.assertNotEqual(self.sd.ready_trigger, int) self.sd = None - self.sd = simulation_objects.simulation_data( ready_trigger=["1"] ) - self.assertEqual( self.sd.ready_trigger, "1" ) + self.sd = simulation_objects.simulation_data(ready_trigger=["1"]) + self.assertEqual(self.sd.ready_trigger, "1") def test_simc_arguments(self): - self.sd = simulation_objects.simulation_data( simc_arguments="1" ) - self.assertNotEqual( self.sd.simc_arguments, "1" ) - self.assertEqual( self.sd.simc_arguments, ["1"] ) + self.sd = simulation_objects.simulation_data(simc_arguments="1") + self.assertNotEqual(self.sd.simc_arguments, "1") + self.assertEqual(self.sd.simc_arguments, ["1"]) self.sd = None - self.sd = simulation_objects.simulation_data( simc_arguments=["a", "b"]) - self.assertEqual( self.sd.simc_arguments, ["a", "b"]) + self.sd = simulation_objects.simulation_data(simc_arguments=["a", "b"]) + self.assertEqual(self.sd.simc_arguments, ["a", "b"]) def test_target_error(self): - self.sd = simulation_objects.simulation_data( target_error="0.5" ) - self.assertEqual( self.sd.target_error, "0.5" ) - self.assertNotEqual( self.sd.target_error, ["1"] ) + self.sd = simulation_objects.simulation_data(target_error="0.5") + self.assertEqual(self.sd.target_error, "0.5") + self.assertNotEqual(self.sd.target_error, ["1"]) self.sd = None - self.sd = simulation_objects.simulation_data( target_error=["a", "b"]) - self.assertEqual( self.sd.target_error, "0.1") + self.sd = simulation_objects.simulation_data(target_error=["a", "b"]) + self.assertEqual(self.sd.target_error, "0.1") def test_threads(self): - self.sd = simulation_objects.simulation_data( threads="1.5" ) - self.assertEqual( self.sd.threads, "1" ) - self.assertNotEqual( self.sd.threads, "1.5" ) + self.sd = simulation_objects.simulation_data(threads="1.5") + self.assertEqual(self.sd.threads, "1") + self.assertNotEqual(self.sd.threads, "1.5") self.sd = None - self.sd = simulation_objects.simulation_data( threads=["a", "b"]) - self.assertEqual( self.sd.threads, "") + self.sd = simulation_objects.simulation_data(threads=["a", "b"]) + self.assertEqual(self.sd.threads, "") + ## ## @brief Tests for all methods of the simulation_data class. ## class TestSimulationDataMethods(unittest.TestCase): + def setUp(self): self.sd = simulation_objects.simulation_data() @@ -229,38 +232,46 @@ def tearDown(self): def test_is_equal(self): sd1 = self.sd sd2 = simulation_objects.simulation_data() - self.assertTrue( sd1.is_equal( sd2 ) ) - self.assertTrue( sd2.is_equal( sd1 ) ) - self.assertTrue( sd1.is_equal( sd1 ) ) - self.assertFalse( sd1.is_equal( "Döner" ) ) - sd_calculate_scale_factors = simulation_objects.simulation_data( calculate_scale_factors="1" ) - self.assertFalse( sd_calculate_scale_factors.is_equal( sd1 ) ) - sd_default_actions = simulation_objects.simulation_data( default_actions="0" ) - self.assertFalse( sd_default_actions.is_equal( sd1 ) ) - sd_default_skill = simulation_objects.simulation_data( default_skill="0.5" ) - self.assertFalse( sd_default_skill.is_equal( sd1 ) ) - sd_executionable = simulation_objects.simulation_data( executionable="1" ) - self.assertFalse( sd_executionable.is_equal( sd1 ) ) - sd_fight_style = simulation_objects.simulation_data( fight_style="helterskelter" ) - self.assertFalse( sd_fight_style.is_equal( sd1 ) ) - sd_fixed_time = simulation_objects.simulation_data( fixed_time="0" ) - self.assertFalse( sd_fixed_time.is_equal( sd1 ) ) - sd_html = simulation_objects.simulation_data( html="test.html" ) - self.assertFalse( sd_html.is_equal( sd1 ) ) - sd_iterations = simulation_objects.simulation_data( iterations="124153" ) - self.assertFalse( sd_iterations.is_equal( sd1 ) ) - sd_log = simulation_objects.simulation_data( log="1" ) - self.assertFalse( sd_log.is_equal( sd1 ) ) - sd_optimize_expressions = simulation_objects.simulation_data( optimize_expressions="0" ) - self.assertFalse( sd_optimize_expressions.is_equal( sd1 ) ) - sd_ptr = simulation_objects.simulation_data( ptr="1" ) - self.assertFalse( sd_ptr.is_equal( sd1 ) ) - sd_ready_trigger = simulation_objects.simulation_data( ready_trigger="0" ) - self.assertFalse( sd_ready_trigger.is_equal( sd1 ) ) - sd_target_error = simulation_objects.simulation_data( target_error="0.5" ) - self.assertFalse( sd_target_error.is_equal( sd1 ) ) - sd_threads = simulation_objects.simulation_data( threads="4" ) - self.assertFalse( sd_threads.is_equal( sd1 ) ) + self.assertTrue(sd1.is_equal(sd2)) + self.assertTrue(sd2.is_equal(sd1)) + self.assertTrue(sd1.is_equal(sd1)) + self.assertFalse(sd1.is_equal("Döner")) + sd_calculate_scale_factors = simulation_objects.simulation_data( + calculate_scale_factors="1" + ) + self.assertFalse(sd_calculate_scale_factors.is_equal(sd1)) + sd_default_actions = simulation_objects.simulation_data( + default_actions="0" + ) + self.assertFalse(sd_default_actions.is_equal(sd1)) + sd_default_skill = simulation_objects.simulation_data(default_skill="0.5") + self.assertFalse(sd_default_skill.is_equal(sd1)) + sd_executionable = simulation_objects.simulation_data(executionable="1") + self.assertFalse(sd_executionable.is_equal(sd1)) + sd_fight_style = simulation_objects.simulation_data( + fight_style="helterskelter" + ) + self.assertFalse(sd_fight_style.is_equal(sd1)) + sd_fixed_time = simulation_objects.simulation_data(fixed_time="0") + self.assertFalse(sd_fixed_time.is_equal(sd1)) + sd_html = simulation_objects.simulation_data(html="test.html") + self.assertFalse(sd_html.is_equal(sd1)) + sd_iterations = simulation_objects.simulation_data(iterations="124153") + self.assertFalse(sd_iterations.is_equal(sd1)) + sd_log = simulation_objects.simulation_data(log="1") + self.assertFalse(sd_log.is_equal(sd1)) + sd_optimize_expressions = simulation_objects.simulation_data( + optimize_expressions="0" + ) + self.assertFalse(sd_optimize_expressions.is_equal(sd1)) + sd_ptr = simulation_objects.simulation_data(ptr="1") + self.assertFalse(sd_ptr.is_equal(sd1)) + sd_ready_trigger = simulation_objects.simulation_data(ready_trigger="0") + self.assertFalse(sd_ready_trigger.is_equal(sd1)) + sd_target_error = simulation_objects.simulation_data(target_error="0.5") + self.assertFalse(sd_target_error.is_equal(sd1)) + sd_threads = simulation_objects.simulation_data(threads="4") + self.assertFalse(sd_threads.is_equal(sd1)) def test_get_dps(self): self.assertEqual(self.sd.get_dps(), None) @@ -268,63 +279,63 @@ def test_get_dps(self): self.assertTrue(self.sd.get_dps(), 12345) def test_set_dps(self): - self.assertTrue( self.sd.set_dps("123") ) + self.assertTrue(self.sd.set_dps("123")) self.assertEqual(self.sd.dps, 123) with self.assertRaises(simulation_objects.AlreadySetError): - self.sd.set_dps( 5678.9 ) - with self.assertRaises( TypeError ): - self.sd.set_dps( "5678", external="Hallo" ) + self.sd.set_dps(5678.9) + with self.assertRaises(TypeError): + self.sd.set_dps("5678", external="Hallo") self.sd = None self.sd = simulation_objects.simulation_data() - with self.assertRaises( TypeError ): + with self.assertRaises(TypeError): self.sd.set_dps("Bonjour", external="Bonsoir") - self.assertEqual( self.sd.get_dps(), None ) - with self.assertRaises( ValueError ): - self.sd.set_dps( "Bonjour", external=False ) - self.assertEqual( self.sd.get_dps(), None ) + self.assertEqual(self.sd.get_dps(), None) + with self.assertRaises(ValueError): + self.sd.set_dps("Bonjour", external=False) + self.assertEqual(self.sd.get_dps(), None) def test_get_avg(self): sd = simulation_objects.simulation_data() - sd.set_dps( 100 ) - self.sd.set_dps( 50 ) - self.assertEqual( sd.get_avg( self.sd ), 75 ) + sd.set_dps(100) + self.sd.set_dps(50) + self.assertEqual(sd.get_avg(self.sd), 75) sd_empty = simulation_objects.simulation_data() - self.assertEqual( self.sd.get_avg( sd_empty ), None ) + self.assertEqual(self.sd.get_avg(sd_empty), None) def test_get_simulation_duration(self): - self.assertEqual( self.sd.get_simulation_duration(), None ) + self.assertEqual(self.sd.get_simulation_duration(), None) self.sd.so_simulation_start_time = datetime.datetime.utcnow() - time.sleep( 0.005 ) - self.assertEqual( self.sd.get_simulation_duration(), None ) + time.sleep(0.005) + self.assertEqual(self.sd.get_simulation_duration(), None) self.sd.so_simulation_end_time = datetime.datetime.utcnow() - self.assertNotEqual( self.sd.get_simulation_duration(), None ) + self.assertNotEqual(self.sd.get_simulation_duration(), None) def test_set_full_report(self): - with self.assertRaises( TypeError ): - self.sd.set_full_report( 1234 ) - report = str( uuid.uuid4() ) - self.assertTrue( self.sd.set_full_report( report ) ) - self.assertEqual( self.sd.full_report, report ) + with self.assertRaises(TypeError): + self.sd.set_full_report(1234) + report = str(uuid.uuid4()) + self.assertTrue(self.sd.set_full_report(report)) + self.assertEqual(self.sd.full_report, report) def test_set_simulation_end_time(self): before = datetime.datetime.utcnow() - self.assertTrue( self.sd.set_simulation_end_time() ) + self.assertTrue(self.sd.set_simulation_end_time()) after = datetime.datetime.utcnow() - self.assertTrue( self.sd.so_simulation_end_time >= before ) - self.assertTrue( self.sd.so_simulation_end_time <= after ) - self.assertEqual( type( self.sd.so_simulation_end_time ), datetime.datetime ) - with self.assertRaises( simulation_objects.AlreadySetError ): + self.assertTrue(self.sd.so_simulation_end_time >= before) + self.assertTrue(self.sd.so_simulation_end_time <= after) + self.assertEqual(type(self.sd.so_simulation_end_time), datetime.datetime) + with self.assertRaises(simulation_objects.AlreadySetError): self.sd.set_simulation_end_time() def test_set_simulation_start_time(self): - self.assertEqual( self.sd.so_simulation_start_time, None ) + self.assertEqual(self.sd.so_simulation_start_time, None) before = datetime.datetime.utcnow() - self.assertTrue( self.sd.set_simulation_start_time() ) + self.assertTrue(self.sd.set_simulation_start_time()) after = datetime.datetime.utcnow() - self.assertTrue( self.sd.so_simulation_start_time >= before ) - self.assertTrue( self.sd.so_simulation_start_time <= after ) - self.assertEqual( type( self.sd.so_simulation_start_time ), datetime.datetime ) - self.assertTrue( self.sd.set_simulation_start_time() ) + self.assertTrue(self.sd.so_simulation_start_time >= before) + self.assertTrue(self.sd.so_simulation_start_time <= after) + self.assertEqual(type(self.sd.so_simulation_start_time), datetime.datetime) + self.assertTrue(self.sd.set_simulation_start_time()) def test_simulate(self): # travis-ci test @@ -336,7 +347,9 @@ def test_simulate(self): # engine/ if os.path.isfile("./SimulationCraft/engine/simc"): self.sd.executionable = "./SimulationCraft/engine/simc" - self.sd.simc_arguments = [ "./SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" ] + self.sd.simc_arguments = [ + "./SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" + ] # local dev test # SimulationCraft/ @@ -346,7 +359,9 @@ def test_simulate(self): # simulation_objects/ elif os.path.isfile("../SimulationCraft/simc.exe"): self.sd.executionable = "../SimulationCraft/simc.exe" - self.sd.simc_arguments = [ "../SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" ] + self.sd.simc_arguments = [ + "../SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" + ] # use standard values # SimulationCraft/ @@ -358,19 +373,21 @@ def test_simulate(self): else: pass self.sd.target_error = "1.0" - self.assertEqual( type( self.sd.simulate() ), int ) - self.assertTrue( self.sd.get_dps() > 0 ) - self.assertEqual( type( self.sd.so_simulation_end_time ), datetime.datetime ) + self.assertEqual(type(self.sd.simulate()), int) + self.assertTrue(self.sd.get_dps() > 0) + self.assertEqual(type(self.sd.so_simulation_end_time), datetime.datetime) def test_simulate_fail(self): self.sd.executionable = "Not_a_correct_value" - with self.assertRaises( FileNotFoundError ): + with self.assertRaises(FileNotFoundError): self.sd.simulate() + ## ## @brief Test handling of init values ## class TestSimulationGroupDataInit(unittest.TestCase): + def setUp(self): self.sd1 = simulation_objects.simulation_data() self.sd2 = simulation_objects.simulation_data() @@ -381,55 +398,58 @@ def tearDown(self): def test_empty(self): with self.assertRaises(TypeError): - test = simulation_objects.simulation_group() + simulation_objects.simulation_group() def test_correct_list_input(self): - test = simulation_objects.simulation_group( [ self.sd1, self.sd2 ] ) - self.assertEqual( test.profiles[0], self.sd1 ) - self.assertEqual( test.profiles[1], self.sd2 ) + test = simulation_objects.simulation_group([self.sd1, self.sd2]) + self.assertEqual(test.profiles[0], self.sd1) + self.assertEqual(test.profiles[1], self.sd2) def test_correct_single_input(self): - test = simulation_objects.simulation_group( self.sd1 ) - self.assertEqual( test.profiles[0], self.sd1 ) + test = simulation_objects.simulation_group(self.sd1) + self.assertEqual(test.profiles[0], self.sd1) def test_wrong_list_input(self): - tmp = simulation_objects.simulation_data( iterations="5000" ) + tmp = simulation_objects.simulation_data(iterations="5000") with self.assertRaises(simulation_objects.InputError): - test = simulation_objects.simulation_group( [ self.sd1, tmp ] ) + simulation_objects.simulation_group([self.sd1, tmp]) with self.assertRaises(TypeError): - test = simulation_objects.simulation_group( [ "why", "would", "anyone", "do", "this" ] ) + simulation_objects.simulation_group([ + "why", "would", "anyone", "do", "this" + ]) def test_wrong_input_type(self): with self.assertRaises(TypeError): - test = simulation_objects.simulation_group( ( self.sd1, self.sd2 ) ) + simulation_objects.simulation_group((self.sd1, self.sd2)) ## ## @brief Test methods of simulation_group ## class TestSimulationGroupMethods(unittest.TestCase): + def setUp(self): - self.sd1 = simulation_objects.simulation_data( target_error="1.0" ) - self.sd2 = simulation_objects.simulation_data( target_error=1.0 ) - self.sg = simulation_objects.simulation_group( [ self.sd1, self.sd2 ] ) + self.sd1 = simulation_objects.simulation_data(target_error="1.0") + self.sd2 = simulation_objects.simulation_data(target_error=1.0) + self.sg = simulation_objects.simulation_group([self.sd1, self.sd2]) def test_selfcheck(self): - self.assertTrue( self.sg.selfcheck() ) + self.assertTrue(self.sg.selfcheck()) self.sg.profiles[1].calculate_scale_factors = "1" - self.assertFalse( self.sg.selfcheck() ) + self.assertFalse(self.sg.selfcheck()) def test_add(self): - start = len( self.sg.profiles ) - new_data = simulation_objects.simulation_data( target_error=1.0) - self.assertTrue( self.sg.add( new_data ) ) - self.assertEqual( len( self.sg.profiles ), start + 1 ) + start = len(self.sg.profiles) + new_data = simulation_objects.simulation_data(target_error=1.0) + self.assertTrue(self.sg.add(new_data)) + self.assertEqual(len(self.sg.profiles), start + 1) with self.assertRaises(TypeError): - self.sg.add( "Bananana" ) + self.sg.add("Bananana") def test_simulate(self): self.sd1.executionable = "Not_a_correct_value" - sole_sg1 = simulation_objects.simulation_group( self.sd1 ) - with self.assertRaises( FileNotFoundError ): + sole_sg1 = simulation_objects.simulation_group(self.sd1) + with self.assertRaises(FileNotFoundError): sole_sg1.simulate() # travis-ci test @@ -441,7 +461,9 @@ def test_simulate(self): # engine/ if os.path.isfile("./SimulationCraft/engine/simc"): self.sd2.executionable = "./SimulationCraft/engine/simc" - self.sd2.simc_arguments = [ "./SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" ] + self.sd2.simc_arguments = [ + "./SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" + ] # local dev test # SimulationCraft/ @@ -451,11 +473,14 @@ def test_simulate(self): # simulation_objects/ elif os.path.isfile("../SimulationCraft/simc.exe"): self.sd2.executionable = "../SimulationCraft/simc.exe" - self.sd2.simc_arguments = [ "../SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" ] - sole_sg2 = simulation_objects.simulation_group( self.sd2 ) - self.assertTrue( sole_sg2.simulate() ) + self.sd2.simc_arguments = [ + "../SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" + ] + sole_sg2 = simulation_objects.simulation_group(self.sd2) + self.assertTrue(sole_sg2.simulate()) self.sg.profiles = None - self.assertFalse( self.sg.simulate() ) + self.assertFalse(self.sg.simulate()) + if __name__ == '__main__': unittest.main() From 3c4a706c3078798038be154c1dcc7804320b916c Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Thu, 22 Feb 2018 10:03:50 +0100 Subject: [PATCH 45/65] Add "In developement note", "Contact", and "Support" to README --- .gitignore | 2 +- README.md | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 96e7d14..d505fd2 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,4 @@ .coverage # local editor -/.vscode \ No newline at end of file +/.vscode diff --git a/README.md b/README.md index 65026aa..b708f84 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ Bloody( tools ) > Automation tool for several different aspects of all dps and some tank specs in World of Warcraft using SimulationCraft. Ingame customization and number tuning make decision making without external help a bloody hell. +## In developement note +This tool is still in developement and can't be used to generate data yet. If you want to contribute or have suggestions to automate data and representation generation, contact me. + ## Requirements You need the latest [SimulationCraft](http://downloads.simulationcraft.org/?C=M;O=D) version ([GitHub Repository](https://github.com/simulationcraft/simc)), [Python 3.5](https://www.python.org/downloads/) or newer and the module [simc_support](https://github.com/Bloodmallet/simc_support), which is handled in the requirements.txt. @@ -28,3 +31,9 @@ $ \Scripts\active ## Development If you see a lack of features somewhere or ways to improve the quality of the code, please contact me or create an [issue](https://github.com/Bloodmallet/bloodytools/issues). + +## Contact +Meet me in [Discord](https://discord.gg/tFR2uvK). There is a channel #bloodytools. My username is Bloodmallet(EU)#8246. + +## Support +If you want to support the developement: [![PayPal link](https://img.shields.io/badge/PayPal-donate-blue.svg)](https://www.paypal.me/bloodmallet) From c58f36a9bfadb906fcbbf5cda3d03f70e619459c Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Thu, 1 Mar 2018 18:18:03 +0100 Subject: [PATCH 46/65] Switch to docstrings, add type hinting --- .gitignore | 3 + .../simulation_objects/simulation_objects.py | 421 ++++++++++-------- .../simulation_objects_tests.py | 39 +- requirements-dev.txt | 1 + 4 files changed, 249 insertions(+), 215 deletions(-) create mode 100644 requirements-dev.txt diff --git a/.gitignore b/.gitignore index d505fd2..80b443c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ # local editor /.vscode + +# local type checks +/.mypy_cache diff --git a/bloodytools/simulation_objects/simulation_objects.py b/bloodytools/simulation_objects/simulation_objects.py index 583555e..aeabc37 100644 --- a/bloodytools/simulation_objects/simulation_objects.py +++ b/bloodytools/simulation_objects/simulation_objects.py @@ -6,6 +6,7 @@ from simc_support import simc_checks as simc_checks # sys operations and data import sys +from typing import Union, List import uuid import logging @@ -13,56 +14,78 @@ logger = logging.getLogger(__name__) -class Error( Exception ): - """Base class for exceptions in this module""" - def __init__( self, message): + +class Error(Exception): + """Base class for exceptions in this module. + """ + + def __init__(self, message: Union[Exception, str]) -> None: self.message = message -class AlreadySetError( Error ): +class AlreadySetError(Error): + """Data is already present. Overwriting is not allowed. + """ + pass -class InputError( Error ): +class NotSetYetError(Error): + """Necessary data for the action was not set yet. + """ + pass -class NotSetYetError( Error ): +class NotStartedYetError(Error): + """Data generation is still in progress. + """ + pass -class StillInProgressError( Error ): +class StillInProgressError(Error): + """Data generation is still in progress. + """ + pass -class SimulationError( Error ): + +class SimulationError(Error): + """Simulation failed. + """ + pass class simulation_data(): - """ - Manages all META-information for a simulation and the result. + """Manages all META-information for a single simulation and the result. TODO: add max_time, vary_combat_length + TODO: dumb the standard values down. There is no need for extended complexity here. The user/creator functions decide what they want/need. Simulation_data is simply a dumb data holder. """ - def __init__( self, - calculate_scale_factors="0", - default_actions="1", - default_skill="1.0", - executionable=None, - fight_style="patchwerk", - fixed_time="1", - html="", - iterations="250000", - log="0", - name="", - optimize_expressions="1", - ptr="0", - ready_trigger="1", - simc_arguments=[], - target_error="0.1", - threads="" ): - - super( simulation_data, self ).__init__() + + def __init__( + self, + calculate_scale_factors: str = "0", + default_actions: str = "1", + default_skill: str = "1.0", + executionable: str = None, + fight_style: str = "patchwerk", + fixed_time: str = "1", + html: str = "", + iterations: str = "250000", + log: str = "0", + name: str = "", + optimize_expressions: str = "1", + ptr: str = "0", + ready_trigger: str = "1", + simc_arguments: list = [], + target_error: str = "0.1", + threads: str = "" + ) -> None: + + super(simulation_data, self).__init__() # simc setting to calculate scale factors (stat weights) if calculate_scale_factors == "0" or calculate_scale_factors == "1": @@ -76,9 +99,9 @@ def __init__( self, self.default_actions = "1" # simc setting to manage the accuracy of used apl lines (leave it at 1.0) try: - self.default_skill = str( float( default_skill ) ) + self.default_skill = str(float(default_skill)) except Exception as e: - logger.error( "{} -- Using default value instead.".format( e ) ) + logger.error("{} -- Using default value instead.".format(e)) self.default_skill = "1.0" # describes the location and type of the simc executable # if no value was set, determine a standard value @@ -91,15 +114,15 @@ def __init__( self, self.executionable = "../simc" else: try: - self.executionable = str( executionable ) + self.executionable = str(executionable) except Exception as e: - logger.error( e ) + logger.error("{}".format(e)) raise e # simc setting to determine the fight style - if fight_style == "custom" or simc_checks.is_fight_style( fight_style ): + if fight_style == "custom" or simc_checks.is_fight_style(fight_style): self.fight_style = fight_style else: - logger.warning( "{} -- Using default value instead.".format( fight_style ) ) + logger.warning("{} -- Using default value instead.".format(fight_style)) self.fight_style = "patchwerk" # simc setting to enable/diable the fixed fight length if fixed_time == "0" or fixed_time == "1": @@ -114,7 +137,7 @@ def __init__( self, # simc setting to determine the maximum number of run iterations # (target_error and iterations determine the actually simulated # iterations count) - self.iterations = str( int( iterations ) ) + self.iterations = str(int(iterations)) # simc setting to enable/disable a log file if log == "0" or log == "1": self.log = log @@ -122,7 +145,7 @@ def __init__( self, self.log = "0" # optional name for the data if not name: - self.name = uuid.uuid4() + self.name = str(uuid.uuid4()) else: self.name = name # simc setting to enable/disable optimize expressions @@ -142,20 +165,20 @@ def __init__( self, self.ready_trigger = "1" # specific data to be run, like talent combinations, specific gear or # traits - if type( simc_arguments ) == list or simc_arguments == []: + if type(simc_arguments) == list or simc_arguments == []: self.simc_arguments = simc_arguments else: self.simc_arguments = [simc_arguments] # simc setting to determine the target_error try: - self.target_error = str( float( target_error ) ) + self.target_error = str(float(target_error)) except Exception as e: self.target_error = "0.1" # simc setting to determine the number of used threads, empty string uses # all available if type(threads) == int or type(threads) == str or type(threads) == float: try: - self.threads = str( int( float( threads ) ) ) + self.threads = str(int(float(threads))) except Exception as e: self.threads = "" else: @@ -165,30 +188,30 @@ def __init__( self, # creation time of the simulation object self.so_creation_time = datetime.datetime.utcnow() # simulation dps result - self.dps = None + self.dps: int # flag to know whether data was generated with external simulation function self.external_simulation = False # simulation full report (command line print out) - self.full_report = None + self.full_report: str # simulation end time - self.so_simulation_end_time = None + self.so_simulation_end_time: datetime.datetime # simulation start time - self.so_simulation_start_time = None + self.so_simulation_start_time: datetime.datetime + def is_equal(self, simulation_instance: 'simulation_data') -> bool: + """Determines if the current and given simulation_data share the + same base. The following attributes are considered base: + calculate_scale_factors, default_actions, default_skill, + executionable, fight_style, fixed_time, html, iterations, log, + optimize_expressions, ptr, ready_trigger, target_error, threads - def is_equal(self, simulation_instance): - """ - @brief Determines if the current and given simulation_data share the - same base. The following attributes are considered base: - calculate_scale_factors, default_actions, default_skill, - executionable, fight_style, fixed_time, html, iterations, log, - optimize_expressions, ptr, ready_trigger, target_error, threads - - @param self The object - @param simulation_instance The other simulation data + Arguments: + simulation_instance {simulation_data} -- Instance of simulation_data - @return True if equal base values, False otherwise. + Returns: + bool -- True if equallity between mentioned data is guaranteed. """ + try: if self.calculate_scale_factors != simulation_instance.calculate_scale_factors: return False @@ -219,162 +242,156 @@ def is_equal(self, simulation_instance): if self.threads != simulation_instance.threads: return False return True - except Exception as e: + except Exception: return False + def get_dps(self) -> int: + """Get the dps of the simulation_instance. - def get_dps( self ): - """ - @brief Get the dps of the simulation. - - @param self The object - - @return The dps. + Returns: + int -- dps """ return self.dps + def set_dps(self, dps: Union[int, float, str], + external: bool = True) -> None: + """Set dps. - def set_dps( self, dps, external=True ): - """ - @brief Sets the dps. + Arguments: + dps {int} -- simulated dps value - @param self The object - @param dps The dps - @param external Set to True if dps was calculated using an external - logic + Keyword Arguments: + external {bool} -- special flag to know whether the core simulation function was used (default: {True}) - @return True if dps was set. + Raises: + TypeError -- Raised if external was not {bool}. + AlreadySetError -- Raised if dps was already set. + e -- Raised if casting dps to {int} fails. """ # set external_simulation flag, defaults to True, so external simulations # don't need to pay attention to this - if type( external ) == bool: + if type(external) == bool: self.external_simulation = external else: raise TypeError # raise AlreadySetError if one tries to overwrite previously set data if self.dps: - raise AlreadySetError( "A value for dps was already set to {}.".format( self.get_dps() ) ) + raise AlreadySetError( + "A value for dps was already set to {}.".format(self.get_dps()) + ) try: # slightly more robust than simply cast to int, due to possible float # numbers as strings - self.dps = int( float( dps ) ) + self.dps = int(float(dps)) except Exception as e: raise e if self.so_simulation_end_time != None: self.so_simulation_end_time = datetime.datetime.utcnow() - return True - - - def get_avg( self, simulation_instance ): - """ - @brief Gets the average of the current the given simulation_instance. + def get_avg(self, simulation_instance: 'simulation_data') -> int: + """Get the average between to the parent and given simulation_instance. - @param self The object - @param simulation_instance The simulation data + Arguments: + simulation_instance {simulation_data} -- A finished simulation_data instance. - @return The average as int. + Returns: + int -- Average of parent and simulation_instance. """ if self.get_dps() and simulation_instance.get_dps(): - return int( ( self.get_dps() + simulation_instance.get_dps() ) / 2 ) + return int((self.get_dps() + simulation_instance.get_dps()) / 2) else: return None + def get_simulation_duration(self) -> datetime.timedelta: + """Return the simulation duration. - def get_simulation_duration( self ): - """ - @brief Return the time, the simulation took. Will raise NotDoneYetError - if simulation didn't start or end yet. - - @param self The object + Raises: + NotDoneYetError -- Raised if simulation is still in progress. - @return The simulation duation. + Returns: + datetime.datetime -- Start time - End time """ + if self.so_simulation_start_time and self.so_simulation_end_time: return self.so_simulation_end_time - self.so_simulation_start_time + elif self.so_simulation_start_time and not self.so_simulation_end_time: + raise StillInProgressError( + "Simulation end time is not set yet. Simulation is probably still in progress." + ) else: - None + raise NotStartedYetError("Simulation didn't start yet.") + def set_full_report(self, report: str) -> None: + """Saves the report (simulation cmd output) to full_report. - def set_full_report( self, report ): - """ - @brief Saves the report (simulation output). + Arguments: + report {str} -- The cmd output of the simulation. - @param self The object - @param report The report - - @return True, if saved. + Raises: + TypeError -- Raised if report was not a string. """ - if type( report ) == str: + if type(report) == str: self.full_report = report - return True else: - raise TypeError("Report of type '{}'' found but string was expected.".format( type( report ) ) ) - + raise TypeError( + "Report of type '{}'' found but string was expected.".format( + type(report) + ) + ) - def set_simulation_end_time( self ): - """ - @brief Set so_simulation_end_time. Raises AlreadySetError if - so_simulation_end_time was already set. - - @param self The object + def set_simulation_end_time(self) -> None: + """Set so_simulation_end_time. - @return True, if set. + Raises: + AlreadySetError -- Raised if the simulation end time was already set. """ if not self.so_simulation_end_time: self.so_simulation_end_time = datetime.datetime.utcnow() - return True else: - raise AlreadySetError( "Simulation end time was already set. Setting it twice is not allowed." ) - - - def set_simulation_start_time( self ): - """ - @brief Set so_simulation_start_time. Can happen multiple times if - simulation failed. - - @param self The object + raise AlreadySetError( + "Simulation end time was already set. Setting it twice is not allowed." + ) - @return True, if set. + def set_simulation_start_time(self) -> None: + """Set so_simulation_start_time. Can be done multiple times. """ self.so_simulation_start_time = datetime.datetime.utcnow() - return True - - def simulate( self ): - """ - @brief Simulates the data using SimulationCraft. Resulting dps are - saved. + def simulate(self) -> int: + """Simulates the data using SimulationCraft. Resulting dps are saved and returned. - @param self The object + Raises: + FileNotFoundError -- Raised if the simulation didn't start due to the executable not being found. + SimulationError -- Raised if the simulation failes multiple times. - @return DPS of the simulation. + Returns: + int -- DPS of the simulation """ - argument = [ self.executionable ] - argument.append( "iterations=" + self.iterations ) - argument.append( "target_error=" + self.target_error ) - argument.append( "fight_style=" + self.fight_style ) - argument.append( "fixed_time=" + self.fixed_time ) - argument.append( "optimize_expressions=" + self.optimize_expressions ) - argument.append( "default_actions=" + self.default_actions ) - argument.append( "log=" + self.log ) - argument.append( "default_skill="+ self.default_skill ) - argument.append( "ptr=" + self.ptr ) - argument.append( "threads=" + self.threads ) + argument = [self.executionable] + argument.append("iterations=" + self.iterations) + argument.append("target_error=" + self.target_error) + argument.append("fight_style=" + self.fight_style) + argument.append("fixed_time=" + self.fixed_time) + argument.append("optimize_expressions=" + self.optimize_expressions) + argument.append("default_actions=" + self.default_actions) + argument.append("log=" + self.log) + argument.append("default_skill=" + self.default_skill) + argument.append("ptr=" + self.ptr) + argument.append("threads=" + self.threads) #argument.append( "ready_trigger="+ self.ready_trigger ) for simc_argument in self.simc_arguments: - argument.append( simc_argument ) + argument.append(simc_argument) fail_counter = 0 # should prevent additional empty windows popping up...on win32 systems without breaking different OS if sys.platform == 'win32': # call simulationcraft in the background. Save output for processing - startupinfo = subprocess.STARTUPINFO() - startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + startupinfo = subprocess.STARTUPINFO() # type: ignore + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW # type: ignore while not hasattr(self, "success") and fail_counter < 5: @@ -414,14 +431,14 @@ def simulate( self ): self.success = True if fail_counter >= 5: - logger.error( "ERROR: An Error occured during simulation." ) - logger.error( "args: " + str( simulation_output.args ) ) - logger.error( "stdout: " + str( simulation_output.stdout ) ) + logger.error("ERROR: An Error occured during simulation.") + logger.error("args: " + str(simulation_output.args)) + logger.error("stdout: " + str(simulation_output.stdout)) self.error = simulation_output.stdout - raise SimulationError( self.error ) + raise SimulationError(self.error) # save output - self.set_full_report( simulation_output.stdout ) + self.set_full_report(simulation_output.stdout) is_actor = True run_dps = "DPS: 0.0" @@ -431,93 +448,116 @@ def simulate( self ): run_dps = line is_actor = False - dps_value = run_dps.split()[ 1 ] + dps_value = run_dps.split()[1] # save dps value - self.set_dps( dps_value, external=False ) + self.set_dps(dps_value, external=False) self.set_simulation_end_time() return self.get_dps() class simulation_group(): + """simulator_group holds one or multiple simulation_data as profiles and can simulate them either serialized or parallel. Parallel uses SimulationCrafts own profilesets feature. Dps values are saved in the simulation_data. """ - @brief simulator_group holds one or multiple simulation_data as profiles - and can simulate them either serialized or parallel. Parallel uses - SimulationCrafts own profilesets feature. Dps values are saved in - the simulation_data. - """ - def __init__( self, simulation_instance, name="" ): + + def __init__( + self, + simulation_instance: Union[simulation_data, List[simulation_data]], + name: str = "" + ) -> None: self.name = name + self.profiles: List[simulation_data] - if type( simulation_instance ) == list: + if type(simulation_instance) == list: correct_type = True - for data in simulation_instance: + for data in simulation_instance: # type: ignore if type(data) != simulation_data: correct_type = False if correct_type: - self.profiles = simulation_instance + self.profiles = simulation_instance # type: ignore else: - raise TypeError( "At least one item of simulation_instance list had a wrong type. Expected simulation_data." ) - elif type( simulation_instance ) == simulation_data: - self.profiles = [ simulation_instance ] + raise TypeError( + "At least one item of simulation_instance list had a wrong type. Expected simulation_data." + ) + elif type(simulation_instance) == simulation_data: + self.profiles = [simulation_instance] # type: ignore else: - raise TypeError( "Simulation_instance has wrong type '{}'. Expected list or single simulation_data.".format(type(simulation_instance)) ) + raise TypeError( + "Simulation_instance has wrong type '{}'. Expected list or single simulation_data.". + format(type(simulation_instance)) + ) if not self.selfcheck(): - raise InputError( "At least one item of simulation_instance had data that didn't match the others." ) + raise ValueError( + "At least one item of simulation_instance had data that didn't match the others." + ) + def selfcheck(self) -> bool: + """Compares the base content of all profiles. All profiles need to + have the same values in each standard field (__init__ of + simulation_data). - def selfcheck( self ): + Returns: + bool -- True if all data is coherent, False otherwise. """ - @brief Compares the base content of all profiles. All profiles need to - have the same values in each standard field (__init__ of - simulation_data). - - @param self The object - @return True if all data is coherent, False otherwise. - """ i = 0 - while i + 1 < len( self.profiles ): - if not self.profiles[ i ].is_equal( self.profiles[ i + 1 ] ): + while i + 1 < len(self.profiles): + if not self.profiles[i].is_equal(self.profiles[i + 1]): return False i += 1 return True + def simulate(self) -> bool: + """Triggers the simulation of all profiles. - def simulate( self ): - """ - @brief Triggers the simulation of all profiles. - - @param self The object + Raises: + e -- Raised if simulation of a single profile failed. + NotImplementedError -- Multi-profile simulation via simulationcraft + profilesets is not yet implemented. + NotSetYetError -- No data available to simulate. - @return True on success. + Returns: + bool -- True if simulations ended successfully. """ + if self.profiles: - if len( self.profiles ) == 1: + if len(self.profiles) == 1: # if only one profiles is in the group this profile is simulated normally try: - self.profiles[ 0 ].simulate() + self.profiles[0].simulate() except Exception as e: raise e - elif len( self.profiles ) >= 2: + elif len(self.profiles) >= 2: # profile set creation based on profiles content # create file with profile set content # start simulation with profilesets # grab data # push data into simulation_data.set_dps(dps) - pass + raise NotImplementedError else: - return False + raise NotSetYetError( + "No profiles were added to this simulation_group yet. Nothing can be simulated." + ) else: return False return True + def add(self, simulation_instance: simulation_data) -> bool: + """Add another simulation_instance object to the group. + + Arguments: + simulation_instance {simulation_data} -- instance of simulation_data + Raises: + e -- Raised if appending a list element files. + TypeError -- Raised if simulation_instance is not of type simulation_data - def add( self, simulation_instance ): + Returns: + bool -- True if added. + """ """ @brief Add another simulation_instance object to the group @@ -526,12 +566,15 @@ def add( self, simulation_instance ): @return True, if adding data was successfull. Exception otherwise. """ - if type( simulation_instance ) == simulation_data: + if type(simulation_instance) == simulation_data: try: - self.profiles.append( simulation_instance ) + self.profiles.append(simulation_instance) except Exception as e: raise e else: return True else: - raise TypeError( "Simulation_instance has wrong type '{}' (needed simulation_data).".format(type(simulation_instance)) ) + raise TypeError( + "Simulation_instance has wrong type '{}' (needed simulation_data).". + format(type(simulation_instance)) + ) diff --git a/bloodytools/simulation_objects/simulation_objects_tests.py b/bloodytools/simulation_objects/simulation_objects_tests.py index 661b3ac..9cb4788 100644 --- a/bloodytools/simulation_objects/simulation_objects_tests.py +++ b/bloodytools/simulation_objects/simulation_objects_tests.py @@ -12,31 +12,22 @@ logger = logging.getLogger(__name__) -## -## @brief Simple tests for the handling of init values for objects of the -## simulation_data class. -## class TestSimulationDataInit(unittest.TestCase): + """Simple tests for the handling of init values for objects of the + simulation_data class. + """ - ## - ## @brief Cleans up after each test. - ## - ## @param self The object - ## - ## @return Nothing - ## def tearDown(self): + """Cleans up after each test. + """ + self.sd = None - ## - ## @brief Test all available and left empty input values for their - ## correct default values. - ## - ## @param self The object - ## - ## @return { description_of_the_return_value } - ## def test_empty(self): + """Test all available and left empty input values for their correct default + values. + """ + self.sd = simulation_objects.simulation_data() self.assertEqual(self.sd.calculate_scale_factors, "0") self.assertEqual(self.sd.default_actions, "1") @@ -59,14 +50,10 @@ def test_empty(self): self.assertEqual(self.sd.so_simulation_end_time, None) self.assertEqual(self.sd.so_simulation_start_time, None) - ## - ## @brief Test input checks of calculate_scale_factors. - ## - ## @param self The object - ## - ## @return { description_of_the_return_value } - ## def test_calculate_scale_factors(self): + """Test input checks of calculate_scale_factors. + """ + self.sd = simulation_objects.simulation_data(calculate_scale_factors="1") self.assertEqual(self.sd.calculate_scale_factors, "1") self.assertNotEqual(self.sd.calculate_scale_factors, "0") diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..f0aa93a --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1 @@ +mypy From 3c8c5e55f4a8a1b458f015cbdadf008426214de3 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Thu, 1 Mar 2018 19:17:24 +0100 Subject: [PATCH 47/65] Update code and tests to match each other and work with type hinting --- .../simulation_objects/simulation_objects.py | 8 ++--- .../simulation_objects_tests.py | 30 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/bloodytools/simulation_objects/simulation_objects.py b/bloodytools/simulation_objects/simulation_objects.py index aeabc37..693c573 100644 --- a/bloodytools/simulation_objects/simulation_objects.py +++ b/bloodytools/simulation_objects/simulation_objects.py @@ -188,15 +188,15 @@ def __init__( # creation time of the simulation object self.so_creation_time = datetime.datetime.utcnow() # simulation dps result - self.dps: int + self.dps: int = None # flag to know whether data was generated with external simulation function self.external_simulation = False # simulation full report (command line print out) - self.full_report: str + self.full_report: str = None # simulation end time - self.so_simulation_end_time: datetime.datetime + self.so_simulation_end_time: datetime.datetime = None # simulation start time - self.so_simulation_start_time: datetime.datetime + self.so_simulation_start_time: datetime.datetime = None def is_equal(self, simulation_instance: 'simulation_data') -> bool: """Determines if the current and given simulation_data share the diff --git a/bloodytools/simulation_objects/simulation_objects_tests.py b/bloodytools/simulation_objects/simulation_objects_tests.py index 9cb4788..db71d38 100644 --- a/bloodytools/simulation_objects/simulation_objects_tests.py +++ b/bloodytools/simulation_objects/simulation_objects_tests.py @@ -13,8 +13,7 @@ class TestSimulationDataInit(unittest.TestCase): - """Simple tests for the handling of init values for objects of the - simulation_data class. + """Simple tests for the handling of init values for objects of the simulation_data class. """ def tearDown(self): @@ -24,8 +23,7 @@ def tearDown(self): self.sd = None def test_empty(self): - """Test all available and left empty input values for their correct default - values. + """Test all available and left empty input values for their correct default values. """ self.sd = simulation_objects.simulation_data() @@ -139,7 +137,7 @@ def test_log(self): def test_name(self): self.sd = simulation_objects.simulation_data(name="") self.assertNotEqual(self.sd.name, "") - self.assertEqual(type(self.sd.name), uuid.UUID) + self.assertEqual(type(self.sd.name), str) self.sd = None self.sd = simulation_objects.simulation_data(name="Borke") self.assertEqual(self.sd.name, "Borke") @@ -266,7 +264,7 @@ def test_get_dps(self): self.assertTrue(self.sd.get_dps(), 12345) def test_set_dps(self): - self.assertTrue(self.sd.set_dps("123")) + self.assertEqual(self.sd.set_dps("123"), None) self.assertEqual(self.sd.dps, 123) with self.assertRaises(simulation_objects.AlreadySetError): self.sd.set_dps(5678.9) @@ -290,23 +288,25 @@ def test_get_avg(self): self.assertEqual(self.sd.get_avg(sd_empty), None) def test_get_simulation_duration(self): - self.assertEqual(self.sd.get_simulation_duration(), None) - self.sd.so_simulation_start_time = datetime.datetime.utcnow() + with self.assertRaises(simulation_objects.NotStartedYetError): + self.assertEqual(self.sd.get_simulation_duration(), None) + self.sd.set_simulation_start_time() time.sleep(0.005) - self.assertEqual(self.sd.get_simulation_duration(), None) - self.sd.so_simulation_end_time = datetime.datetime.utcnow() + with self.assertRaises(simulation_objects.StillInProgressError): + self.assertEqual(self.sd.get_simulation_duration(), None) + self.sd.set_simulation_end_time() self.assertNotEqual(self.sd.get_simulation_duration(), None) def test_set_full_report(self): with self.assertRaises(TypeError): self.sd.set_full_report(1234) report = str(uuid.uuid4()) - self.assertTrue(self.sd.set_full_report(report)) + self.assertEqual(self.sd.set_full_report(report), None) self.assertEqual(self.sd.full_report, report) def test_set_simulation_end_time(self): before = datetime.datetime.utcnow() - self.assertTrue(self.sd.set_simulation_end_time()) + self.assertEqual(self.sd.set_simulation_end_time(), None) after = datetime.datetime.utcnow() self.assertTrue(self.sd.so_simulation_end_time >= before) self.assertTrue(self.sd.so_simulation_end_time <= after) @@ -317,12 +317,12 @@ def test_set_simulation_end_time(self): def test_set_simulation_start_time(self): self.assertEqual(self.sd.so_simulation_start_time, None) before = datetime.datetime.utcnow() - self.assertTrue(self.sd.set_simulation_start_time()) + self.assertEqual(self.sd.set_simulation_start_time(), None) after = datetime.datetime.utcnow() self.assertTrue(self.sd.so_simulation_start_time >= before) self.assertTrue(self.sd.so_simulation_start_time <= after) self.assertEqual(type(self.sd.so_simulation_start_time), datetime.datetime) - self.assertTrue(self.sd.set_simulation_start_time()) + self.assertEqual(self.sd.set_simulation_start_time(), None) def test_simulate(self): # travis-ci test @@ -398,7 +398,7 @@ def test_correct_single_input(self): def test_wrong_list_input(self): tmp = simulation_objects.simulation_data(iterations="5000") - with self.assertRaises(simulation_objects.InputError): + with self.assertRaises(ValueError): simulation_objects.simulation_group([self.sd1, tmp]) with self.assertRaises(TypeError): simulation_objects.simulation_group([ From a0f2134372f73bebdae3eab7e673cae6c8a1cee6 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 26 Mar 2018 20:12:36 +0200 Subject: [PATCH 48/65] copy simc executable to the main directory --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e9b949a..7331817 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,8 +28,10 @@ install: - cd SimulationCraft/engine # Build SimulationCraft executable - make optimized + - cp simc .. + - cd .. - chmod +x simc - - cd ../.. + - cd .. script: # Test simulation_objects and collect coverage information at the same time From c45fa81f5479d650dbd4f7f1e5b90bbd58f04c1e Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Mon, 26 Mar 2018 23:42:24 +0200 Subject: [PATCH 49/65] add profilesets and tests --- .../simulation_objects/simulation_objects.py | 206 ++++++++++++++++-- .../simulation_objects_tests.py | 78 +++++-- 2 files changed, 243 insertions(+), 41 deletions(-) diff --git a/bloodytools/simulation_objects/simulation_objects.py b/bloodytools/simulation_objects/simulation_objects.py index 693c573..e42dd81 100644 --- a/bloodytools/simulation_objects/simulation_objects.py +++ b/bloodytools/simulation_objects/simulation_objects.py @@ -4,6 +4,8 @@ import datetime # wow game data and simc input checks from simc_support import simc_checks as simc_checks +# required for file existance check +import os # sys operations and data import sys from typing import Union, List @@ -70,7 +72,7 @@ def __init__( calculate_scale_factors: str = "0", default_actions: str = "1", default_skill: str = "1.0", - executionable: str = None, + executable: str = None, fight_style: str = "patchwerk", fixed_time: str = "1", html: str = "", @@ -105,16 +107,20 @@ def __init__( self.default_skill = "1.0" # describes the location and type of the simc executable # if no value was set, determine a standard value - if executionable == None: + if executable == None: if sys.platform == 'win32': - logger.debug("Setting Windows default value for executionable.") - self.executionable = "../simc.exe" + logger.info( + "Setting Windows default value for executable. This might not work for your system." + ) + self.executable = "../simc.exe" else: - logger.debug("Setting Linux default value for executionable.") - self.executionable = "../simc" + logger.info( + "Setting Linux default value for executable. This might not work for your system." + ) + self.executable = "../simc" else: try: - self.executionable = str(executionable) + self.executable = str(executable) except Exception as e: logger.error("{}".format(e)) raise e @@ -202,7 +208,7 @@ def is_equal(self, simulation_instance: 'simulation_data') -> bool: """Determines if the current and given simulation_data share the same base. The following attributes are considered base: calculate_scale_factors, default_actions, default_skill, - executionable, fight_style, fixed_time, html, iterations, log, + executable, fight_style, fixed_time, html, iterations, log, optimize_expressions, ptr, ready_trigger, target_error, threads Arguments: @@ -219,7 +225,7 @@ def is_equal(self, simulation_instance: 'simulation_data') -> bool: return False if self.default_skill != simulation_instance.default_skill: return False - if self.executionable != simulation_instance.executionable: + if self.executable != simulation_instance.executable: return False if self.fight_style != simulation_instance.fight_style: return False @@ -370,7 +376,7 @@ def simulate(self) -> int: Returns: int -- DPS of the simulation """ - argument = [self.executionable] + argument = [self.executable] argument.append("iterations=" + self.iterations) argument.append("target_error=" + self.target_error) argument.append("fight_style=" + self.fight_style) @@ -381,11 +387,12 @@ def simulate(self) -> int: argument.append("default_skill=" + self.default_skill) argument.append("ptr=" + self.ptr) argument.append("threads=" + self.threads) - #argument.append( "ready_trigger="+ self.ready_trigger ) for simc_argument in self.simc_arguments: argument.append(simc_argument) + argument.append("ready_trigger=" + self.ready_trigger) + fail_counter = 0 # should prevent additional empty windows popping up...on win32 systems without breaking different OS if sys.platform == 'win32': @@ -464,11 +471,20 @@ class simulation_group(): def __init__( self, simulation_instance: Union[simulation_data, List[simulation_data]], - name: str = "" + name: str = "", + threads: str = "", + profileset_work_threads: str = "", + executable: str = "" ) -> None: self.name = name + self.filename: str = "" + self.threads = threads + # simulationcrafts own multithreading + self.profileset_work_threads = profileset_work_threads + self.executable = executable self.profiles: List[simulation_data] + # check input types if type(simulation_instance) == list: correct_type = True for data in simulation_instance: # type: ignore @@ -488,10 +504,9 @@ def __init__( format(type(simulation_instance)) ) + # check input values if not self.selfcheck(): - raise ValueError( - "At least one item of simulation_instance had data that didn't match the others." - ) + raise ValueError("Selfcheck of the simulation_group failed.") def selfcheck(self) -> bool: """Compares the base content of all profiles. All profiles need to @@ -507,6 +522,7 @@ def selfcheck(self) -> bool: if not self.profiles[i].is_equal(self.profiles[i + 1]): return False i += 1 + return True def simulate(self) -> bool: @@ -521,7 +537,6 @@ def simulate(self) -> bool: Returns: bool -- True if simulations ended successfully. """ - if self.profiles: if len(self.profiles) == 1: # if only one profiles is in the group this profile is simulated normally @@ -529,13 +544,160 @@ def simulate(self) -> bool: self.profiles[0].simulate() except Exception as e: raise e + elif len(self.profiles) >= 2: - # profile set creation based on profiles content - # create file with profile set content - # start simulation with profilesets - # grab data - # push data into simulation_data.set_dps(dps) - raise NotImplementedError + + # check for a path to executable + if not self.executable: + raise ValueError( + "No path_to_executable was set. Simulation can't start." + ) + + # write data to file, create file name + if self.filename: + raise AlreadySetError( + "Filename '{}' was already set for the simulation_group. You probably tried to simulate the same group twice.". + format(self.filename) + ) + else: + self.filename = str(uuid.uuid4()) + ".simc" + + # if somehow the random naming function created the same name twice + if os.path.isfile(self.filename): + logger.info( + "Somehow the random filename generator generated a name ('{}') that is already on use.". + format(self.filename) + ) + self.filename = str(uuid.uuid4()) + ".simc" + # write arguments to file + with open(self.filename, "w") as f: + # write the equal values to file + f.write( + "calculate_scale_factors={}\n".format( + self.profiles[0].calculate_scale_factors + ) + ) + f.write( + "default_actions={}\n".format(self.profiles[0].default_actions) + ) + f.write( + "default_skill={}\n".format(self.profiles[0].default_skill) + ) + f.write("fight_style={}\n".format(self.profiles[0].fight_style)) + f.write("fixed_time={}\n".format(self.profiles[0].fixed_time)) + f.write("html={}\n".format(self.profiles[0].html)) + f.write("iterations={}\n".format(self.profiles[0].iterations)) + f.write("log={}\n".format(self.profiles[0].log)) + f.write( + "optimize_expressions={}\n".format( + self.profiles[0].optimize_expressions + ) + ) + f.write("ptr={}\n".format(self.profiles[0].ptr)) + f.write("target_error={}\n".format(self.profiles[0].target_error)) + f.write("threads={}\n".format(self.threads)) + f.write( + "profileset_work_threads={}\n".format( + self.profileset_work_threads + ) + ) + + # write all specific arguments to file + for profile in self.profiles: + # first used profile needs to be written as normal profile instead of profileset + if profile == self.profiles[0]: + for argument in profile.simc_arguments: + f.write("{}\n".format(argument)) + f.write("name={}\n\n# Profileset start\n".format(profile.name)) + # or else in wrong scope + f.write( + "ready_trigger={}\n".format(self.profiles[0].ready_trigger) + ) + + else: + for special_argument in profile.simc_arguments: + f.write( + "profileset.\"{profile_name}\"+={argument}\n".format( + profile_name=profile.name, argument=special_argument + ) + ) + + # counter of failed simulation attempts + fail_counter = 0 + # should prevent additional empty windows popping up...on win32 systems without breaking different OS + if sys.platform == 'win32': + # call simulationcraft in the background. Save output for processing + startupinfo = subprocess.STARTUPINFO() # type: ignore + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW # type: ignore + + while not hasattr(self, "success") and fail_counter < 5: + + try: + simulation_output = subprocess.run( + [self.executable, self.filename], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + startupinfo=startupinfo + ) + except FileNotFoundError as e: + raise e + + if simulation_output.returncode != 0: + fail_counter += 1 + else: + self.success = True + + else: + + while not hasattr(self, "success") and fail_counter < 5: + + try: + simulation_output = subprocess.run( + [self.executable, self.filename], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True + ) + except FileNotFoundError as e: + raise e + + if simulation_output.returncode != 0: + fail_counter += 1 + else: + self.success = True + + if fail_counter >= 5: + logger.error("ERROR: An Error occured during simulation.") + logger.error("args: " + str(simulation_output.args)) + logger.error("stdout: " + str(simulation_output.stdout)) + logger.error( + "name=value error? Check your input! Maybe your links to already existing profiles are wrong. They need to be relative links from bloodytools to your SimulationCraft directory." + ) + self.error = simulation_output.stdout + raise SimulationError(self.error) + + # get dps of the first profile + is_actor = True + profileset_results = False + for line in simulation_output.stdout.splitlines(): + # needs this check to prevent grabbing the boss dps + if "DPS:" in line and is_actor: + self.profiles[0].set_dps(line.split()[1], external=False) + is_actor = False + + if "Profilesets (median Damage per Second):" in line: + profileset_results = True + + # get dps values of the profileset-simulations + if profileset_results: + for profile in self.profiles: + if profile.name in line: + profile.set_dps(line.split()[0], external=False) + + # remove profilesets file + os.remove(self.filename) + else: raise NotSetYetError( "No profiles were added to this simulation_group yet. Nothing can be simulated." diff --git a/bloodytools/simulation_objects/simulation_objects_tests.py b/bloodytools/simulation_objects/simulation_objects_tests.py index db71d38..b26853a 100644 --- a/bloodytools/simulation_objects/simulation_objects_tests.py +++ b/bloodytools/simulation_objects/simulation_objects_tests.py @@ -231,8 +231,8 @@ def test_is_equal(self): self.assertFalse(sd_default_actions.is_equal(sd1)) sd_default_skill = simulation_objects.simulation_data(default_skill="0.5") self.assertFalse(sd_default_skill.is_equal(sd1)) - sd_executionable = simulation_objects.simulation_data(executionable="1") - self.assertFalse(sd_executionable.is_equal(sd1)) + sd_executable = simulation_objects.simulation_data(executable="1") + self.assertFalse(sd_executable.is_equal(sd1)) sd_fight_style = simulation_objects.simulation_data( fight_style="helterskelter" ) @@ -333,7 +333,7 @@ def test_simulate(self): # SimulationCraft/ # engine/ if os.path.isfile("./SimulationCraft/engine/simc"): - self.sd.executionable = "./SimulationCraft/engine/simc" + self.sd.executable = "./SimulationCraft/engine/simc" self.sd.simc_arguments = [ "./SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" ] @@ -345,7 +345,7 @@ def test_simulate(self): # bloodytools/ # simulation_objects/ elif os.path.isfile("../SimulationCraft/simc.exe"): - self.sd.executionable = "../SimulationCraft/simc.exe" + self.sd.executable = "../SimulationCraft/simc.exe" self.sd.simc_arguments = [ "../SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" ] @@ -365,7 +365,7 @@ def test_simulate(self): self.assertEqual(type(self.sd.so_simulation_end_time), datetime.datetime) def test_simulate_fail(self): - self.sd.executionable = "Not_a_correct_value" + self.sd.executable = "Not_a_correct_value" with self.assertRaises(FileNotFoundError): self.sd.simulate() @@ -416,8 +416,12 @@ def test_wrong_input_type(self): class TestSimulationGroupMethods(unittest.TestCase): def setUp(self): - self.sd1 = simulation_objects.simulation_data(target_error="1.0") - self.sd2 = simulation_objects.simulation_data(target_error=1.0) + self.sd1 = simulation_objects.simulation_data( + target_error="1.0", simc_arguments=["talents=2222222"] + ) + self.sd2 = simulation_objects.simulation_data( + target_error=1.0, simc_arguments=["talents=1111111"] + ) self.sg = simulation_objects.simulation_group([self.sd1, self.sd2]) def test_selfcheck(self): @@ -433,8 +437,8 @@ def test_add(self): with self.assertRaises(TypeError): self.sg.add("Bananana") - def test_simulate(self): - self.sd1.executionable = "Not_a_correct_value" + def test_simulate_single_data(self): + self.sd1.executable = "Not_a_correct_value" sole_sg1 = simulation_objects.simulation_group(self.sd1) with self.assertRaises(FileNotFoundError): sole_sg1.simulate() @@ -445,12 +449,11 @@ def test_simulate(self): # bloodytools/ # simulation_objects/ # SimulationCraft/ - # engine/ - if os.path.isfile("./SimulationCraft/engine/simc"): - self.sd2.executionable = "./SimulationCraft/engine/simc" - self.sd2.simc_arguments = [ - "./SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" - ] + if os.path.isfile("./SimulationCraft/simc"): + self.sd2.executable = "./SimulationCraft/simc" + self.sd2.simc_arguments.insert( + 0, "./SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" + ) # local dev test # SimulationCraft/ @@ -459,15 +462,52 @@ def test_simulate(self): # bloodytools/ # simulation_objects/ elif os.path.isfile("../SimulationCraft/simc.exe"): - self.sd2.executionable = "../SimulationCraft/simc.exe" - self.sd2.simc_arguments = [ - "../SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" - ] + self.sd2.executable = "../SimulationCraft/simc.exe" + self.sd2.simc_arguments.insert( + 0, "../SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" + ) sole_sg2 = simulation_objects.simulation_group(self.sd2) self.assertTrue(sole_sg2.simulate()) self.sg.profiles = None self.assertFalse(self.sg.simulate()) + def test_simulate_profilesets_wrong_path(self): + self.sg.executable = "Not_a_correct_value" + with self.assertRaises(FileNotFoundError): + self.sg.simulate() + os.remove(self.sg.filename) + + def test_simulate_profilesets_correct_path(self): + # travis-ci test + # bloodytools/ + # ./ + # bloodytools/ + # simulation_objects/ + # SimulationCraft/ + if os.path.isfile("./SimulationCraft/simc"): + self.sg.executable = "./SimulationCraft/simc" + self.sg.profiles[0].simc_arguments.insert( + 0, "./SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" + ) + + # local dev test + # SimulationCraft/ + # bloodytools/ + # ./ + # bloodytools/ + # simulation_objects/ + elif os.path.isfile("../SimulationCraft/simc.exe"): + self.sg.executable = "../SimulationCraft/simc.exe" + self.sg.profiles[0].simc_arguments.insert( + 0, "../SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" + ) + self.sg.profiles[1].simc_arguments = ["talents=3333333"] + self.assertTrue(self.sg.simulate()) + + def test_simulate_profilesets_no_profiles(self): + self.sg.profiles = None + self.assertFalse(self.sg.simulate()) + if __name__ == '__main__': unittest.main() From d11d9071e3160a212699086f85dc56505c3a9ce8 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Tue, 27 Mar 2018 08:30:47 +0200 Subject: [PATCH 50/65] Add simulation time to simulation groups --- .../simulation_objects/simulation_objects.py | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/bloodytools/simulation_objects/simulation_objects.py b/bloodytools/simulation_objects/simulation_objects.py index e42dd81..bdec6ed 100644 --- a/bloodytools/simulation_objects/simulation_objects.py +++ b/bloodytools/simulation_objects/simulation_objects.py @@ -294,9 +294,6 @@ def set_dps(self, dps: Union[int, float, str], except Exception as e: raise e - if self.so_simulation_end_time != None: - self.so_simulation_end_time = datetime.datetime.utcnow() - def get_avg(self, simulation_instance: 'simulation_data') -> int: """Get the average between to the parent and given simulation_instance. @@ -483,6 +480,8 @@ def __init__( self.profileset_work_threads = profileset_work_threads self.executable = executable self.profiles: List[simulation_data] + self.sg_simulation_start_time: datetime.datetime = None + self.sg_simulation_end_time: datetime.datetime = None # check input types if type(simulation_instance) == list: @@ -525,6 +524,24 @@ def selfcheck(self) -> bool: return True + def set_simulation_end_time(self) -> None: + """Set sg_simulation_end_time. + + Raises: + AlreadySetError -- Raised if the simulation end time was already set. + """ + if not self.sg_simulation_end_time: + self.sg_simulation_end_time = datetime.datetime.utcnow() + else: + raise AlreadySetError( + "Simulation end time was already set. Setting it twice is not allowed." + ) + + def set_simulation_start_time(self) -> None: + """Set sg_simulation_start_time. Can be done multiple times. + """ + self.sg_simulation_start_time = datetime.datetime.utcnow() + def simulate(self) -> bool: """Triggers the simulation of all profiles. @@ -538,6 +555,9 @@ def simulate(self) -> bool: bool -- True if simulations ended successfully. """ if self.profiles: + + self.set_simulation_start_time() + if len(self.profiles) == 1: # if only one profiles is in the group this profile is simulated normally try: @@ -702,6 +722,9 @@ def simulate(self) -> bool: raise NotSetYetError( "No profiles were added to this simulation_group yet. Nothing can be simulated." ) + + self.set_simulation_end_time() + else: return False From cb20747a5957acc44169439d567df4ca63c41d3f Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Tue, 27 Mar 2018 13:55:40 +0200 Subject: [PATCH 51/65] update simc_support version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5c032e7..d4b5c2e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ # Lib with World of Warcraft game data for simulations and some input checks for SimulationCraft. --e git+https://github.com/Bloodmallet/simc_support.git@1.0.1#egg=simc_support +-e git+https://github.com/Bloodmallet/simc_support.git@v1.0.2#egg=simc_support From 2f0c7d4a65e2b2f1fd810cc37f676986a35c2bc0 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Tue, 27 Mar 2018 13:58:40 +0200 Subject: [PATCH 52/65] fix logger usage --- .../simulation_objects/simulation_objects.py | 67 ++++++++++++------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/bloodytools/simulation_objects/simulation_objects.py b/bloodytools/simulation_objects/simulation_objects.py index bdec6ed..1d71063 100644 --- a/bloodytools/simulation_objects/simulation_objects.py +++ b/bloodytools/simulation_objects/simulation_objects.py @@ -1,4 +1,5 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- # date data import datetime @@ -14,8 +15,6 @@ import subprocess -logger = logging.getLogger(__name__) - class Error(Exception): """Base class for exceptions in this module. @@ -84,11 +83,15 @@ def __init__( ready_trigger: str = "1", simc_arguments: list = [], target_error: str = "0.1", - threads: str = "" + threads: str = "", + logger: logging.Logger = None ) -> None: super(simulation_data, self).__init__() + self.logger = logger or logging.getLogger(__name__) + self.logger.debug("simulation_data initiated.") + # simc setting to calculate scale factors (stat weights) if calculate_scale_factors == "0" or calculate_scale_factors == "1": self.calculate_scale_factors = calculate_scale_factors @@ -103,18 +106,18 @@ def __init__( try: self.default_skill = str(float(default_skill)) except Exception as e: - logger.error("{} -- Using default value instead.".format(e)) + self.logger.error("{} -- Using default value instead.".format(e)) self.default_skill = "1.0" # describes the location and type of the simc executable # if no value was set, determine a standard value if executable == None: if sys.platform == 'win32': - logger.info( + self.logger.info( "Setting Windows default value for executable. This might not work for your system." ) self.executable = "../simc.exe" else: - logger.info( + self.logger.info( "Setting Linux default value for executable. This might not work for your system." ) self.executable = "../simc" @@ -122,13 +125,15 @@ def __init__( try: self.executable = str(executable) except Exception as e: - logger.error("{}".format(e)) + self.logger.error("{}".format(e)) raise e # simc setting to determine the fight style if fight_style == "custom" or simc_checks.is_fight_style(fight_style): self.fight_style = fight_style else: - logger.warning("{} -- Using default value instead.".format(fight_style)) + self.logger.warning( + "{} -- Using default value instead.".format(fight_style) + ) self.fight_style = "patchwerk" # simc setting to enable/diable the fixed fight length if fixed_time == "0" or fixed_time == "1": @@ -284,7 +289,9 @@ def set_dps(self, dps: Union[int, float, str], # raise AlreadySetError if one tries to overwrite previously set data if self.dps: raise AlreadySetError( - "A value for dps was already set to {}.".format(self.get_dps()) + "Profile '{}' already had its dps value set to {}.".format( + self.name, self.get_dps() + ) ) try: @@ -293,6 +300,9 @@ def set_dps(self, dps: Union[int, float, str], self.dps = int(float(dps)) except Exception as e: raise e + self.logger.debug( + "Set DPS of profile '{}' to {}.".format(self.name, self.get_dps()) + ) def get_avg(self, simulation_instance: 'simulation_data') -> int: """Get the average between to the parent and given simulation_instance. @@ -435,9 +445,9 @@ def simulate(self) -> int: self.success = True if fail_counter >= 5: - logger.error("ERROR: An Error occured during simulation.") - logger.error("args: " + str(simulation_output.args)) - logger.error("stdout: " + str(simulation_output.stdout)) + self.logger.error("ERROR: An Error occured during simulation.") + self.logger.error("args: " + str(simulation_output.args)) + self.logger.error("stdout: " + str(simulation_output.stdout)) self.error = simulation_output.stdout raise SimulationError(self.error) @@ -467,12 +477,16 @@ class simulation_group(): def __init__( self, - simulation_instance: Union[simulation_data, List[simulation_data]], + simulation_instance: Union[simulation_data, List[simulation_data]] = None, name: str = "", threads: str = "", profileset_work_threads: str = "", - executable: str = "" + executable: str = "", + logger: logging.Logger = None ) -> None: + self.logger = logger or logging.getLogger(__name__) + self.logger.debug("simulation_group initiated.") + self.name = name self.filename: str = "" self.threads = threads @@ -484,7 +498,9 @@ def __init__( self.sg_simulation_end_time: datetime.datetime = None # check input types - if type(simulation_instance) == list: + if not simulation_instance: + self.profiles = [] + elif type(simulation_instance) == list: correct_type = True for data in simulation_instance: # type: ignore if type(data) != simulation_data: @@ -547,8 +563,6 @@ def simulate(self) -> bool: Raises: e -- Raised if simulation of a single profile failed. - NotImplementedError -- Multi-profile simulation via simulationcraft - profilesets is not yet implemented. NotSetYetError -- No data available to simulate. Returns: @@ -584,7 +598,7 @@ def simulate(self) -> bool: # if somehow the random naming function created the same name twice if os.path.isfile(self.filename): - logger.info( + self.logger.info( "Somehow the random filename generator generated a name ('{}') that is already on use.". format(self.filename) ) @@ -688,18 +702,21 @@ def simulate(self) -> bool: self.success = True if fail_counter >= 5: - logger.error("ERROR: An Error occured during simulation.") - logger.error("args: " + str(simulation_output.args)) - logger.error("stdout: " + str(simulation_output.stdout)) - logger.error( + self.logger.error("ERROR: An Error occured during simulation.") + self.logger.error("args: " + str(simulation_output.args)) + self.logger.error("stdout: " + str(simulation_output.stdout)) + self.logger.error( "name=value error? Check your input! Maybe your links to already existing profiles are wrong. They need to be relative links from bloodytools to your SimulationCraft directory." ) self.error = simulation_output.stdout raise SimulationError(self.error) + self.logger.debug(simulation_output.stdout) + # get dps of the first profile is_actor = True profileset_results = False + for line in simulation_output.stdout.splitlines(): # needs this check to prevent grabbing the boss dps if "DPS:" in line and is_actor: @@ -715,6 +732,10 @@ def simulate(self) -> bool: if profile.name in line: profile.set_dps(line.split()[0], external=False) + # prevents false positive from Waiting -data + if "" == line and profileset_results: + profileset_results = False + # remove profilesets file os.remove(self.filename) From 7c81831adddc6e92da1049b59b1aaf7fab04aa0e Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Tue, 27 Mar 2018 13:59:20 +0200 Subject: [PATCH 53/65] add encoding --- bloodytools/simulation_objects/simulation_objects_tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bloodytools/simulation_objects/simulation_objects_tests.py b/bloodytools/simulation_objects/simulation_objects_tests.py index b26853a..1eb22c0 100644 --- a/bloodytools/simulation_objects/simulation_objects_tests.py +++ b/bloodytools/simulation_objects/simulation_objects_tests.py @@ -1,4 +1,5 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- import datetime import logging From e39d523b811a09ae7fbe5d38479e7f8f1288b296 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Tue, 27 Mar 2018 14:00:02 +0200 Subject: [PATCH 54/65] add rough scatch for one functionality (race sims) --- bloodytools/bloodytools.py | 104 +++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 bloodytools/bloodytools.py diff --git a/bloodytools/bloodytools.py b/bloodytools/bloodytools.py new file mode 100644 index 0000000..639eb26 --- /dev/null +++ b/bloodytools/bloodytools.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import logging +import settings +import simulation_objects.simulation_objects as simulation_objects +from simc_support import wow_lib + +# activate logging to file and console +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) +# file logger +fh = logging.FileHandler("log.txt", "w") +fh.setLevel(logging.ERROR) +if hasattr(settings, "debug"): + if settings.debug: + fh.setLevel(logging.DEBUG) +log_formatter = logging.Formatter( + "%(asctime)s - %(filename)s / %(funcName)s - %(levelname)s - %(message)s" +) +fh.setFormatter(log_formatter) +logger.addHandler(fh) +# console logger +ch = logging.StreamHandler() +ch.setLevel(logging.DEBUG) +ch.setFormatter(log_formatter) +logger.addHandler(ch) + + +def race_simulations(specs: [(str, str)]): + logger.debug("Started") + for fight_style in settings.fight_styles: + for wow_class, wow_spec in specs: + races = wow_lib.get_races_of_class(wow_class) + simulation_group = simulation_objects.simulation_group( + name="race_simulations", + threads=settings.threads, + profileset_work_threads=settings.profileset_work_threads, + executable=settings.executable, + logger=logger + ) + + for race in races: + + simulation_data = None + + if race == races[0]: + # create the basis profile string + basis_profile_string = settings.executable.split("simc")[0] + basis_profile_string += "profiles/" + if settings.tier == "PR": + basis_profile_string += "PreRaids/" + else: + basis_profile_string += "Tier{}/".format(settings.tier) + basis_profile_string += "T{}_{}_{}".format( + settings.tier, wow_class, wow_spec + ).title() + basis_profile_string += ".simc" + + logger.debug( + "Created basis_profile_string '{}'.".format(basis_profile_string) + ) + + simulation_data = simulation_objects.simulation_data( + name=race, + fight_style=fight_style, + simc_arguments=[basis_profile_string, "race={}".format(race)], + logger=logger + ) + else: + simulation_data = simulation_objects.simulation_data( + name=race, + fight_style=fight_style, + simc_arguments=["race={}".format(race)], + logger=logger + ) + simulation_group.add(simulation_data) + logger.debug(( + "Added race '{}' in profile '{}' to simulation_group.".format( + simulation_data.name, race + ) + )) + logger.debug("Start simulation.") + simulation_group.simulate() + logger.debug("Finished simulation.") + for profile in simulation_group.profiles: + logger.debug( + "Profile '{}' DPS: {}".format(profile.name, profile.get_dps()) + ) + + logger.debug("Ended") + pass + + +def main(): + logger.debug("Started") + race_simulations([("shaman", "elemental")]) + logger.debug("Ended") + + +if __name__ == '__main__': + logger.debug("__main__ started") + main() + logger.debug("__main__ ended") From 48340d07d4f1dfcc8d425899478b81bf847a6291 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Tue, 27 Mar 2018 14:00:24 +0200 Subject: [PATCH 55/65] add empty test suite for the main file --- bloodytools/bloodytools_test.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 bloodytools/bloodytools_test.py diff --git a/bloodytools/bloodytools_test.py b/bloodytools/bloodytools_test.py new file mode 100644 index 0000000..608ec00 --- /dev/null +++ b/bloodytools/bloodytools_test.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import logging +import unittest + +import bloodytools + +logger = logging.getLogger(__name__) + + +class Test(unittest.TestCase): + """Simple tests for the handling of init values for objects of the simulation_data class. + """ + + def setUp(self): + """Prepares before each test. + """ + + pass + + def tearDown(self): + """Cleans up after each test. + """ + + pass + + def test_empty(self): + """Test all available and left empty input values for their correct default values. + """ + + pass From fc0500eeb7b9e7324570501c85c60232f8b91aad Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Tue, 27 Mar 2018 14:02:31 +0200 Subject: [PATCH 56/65] add basefile for settings --- bloodytools/settings.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 bloodytools/settings.py diff --git a/bloodytools/settings.py b/bloodytools/settings.py new file mode 100644 index 0000000..d9e21ca --- /dev/null +++ b/bloodytools/settings.py @@ -0,0 +1,11 @@ +# Profile setttings +tier = "21" # number or PR (PreRaid) +fight_styles = ["patchwerk", "beastlord"] + +# Simulation settings +executable = "../../SimulationCraft/simc.exe" +threads = "8" +profileset_work_threads = "2" + +# General Bloodytools Settings - you usually don't need to touch these +debug = True From 1e7a082403e91d4563929ea0759eabfeb0b34b85 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Fri, 11 May 2018 16:03:45 +0200 Subject: [PATCH 57/65] big update --- bloodytools/bloodytools.py | 591 +++++++++++++++++- bloodytools/settings.py | 19 +- .../simulation_objects/simulation_objects.py | 53 +- .../simulation_objects_tests.py | 124 ++-- 4 files changed, 677 insertions(+), 110 deletions(-) diff --git a/bloodytools/bloodytools.py b/bloodytools/bloodytools.py index 639eb26..ce9762f 100644 --- a/bloodytools/bloodytools.py +++ b/bloodytools/bloodytools.py @@ -1,6 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +from typing import List, Tuple + +import datetime +import json import logging import settings import simulation_objects.simulation_objects as simulation_objects @@ -27,12 +31,117 @@ logger.addHandler(ch) -def race_simulations(specs: [(str, str)]): - logger.debug("Started") +def create_basic_profile_string(wow_class: str, wow_spec: str, tier: str): + """Create basic profile string to get the standard profile of a spec. Use this function to get the necessary string for your first argument of a simulation_data object. + + Arguments: + wow_class {str} -- wow class, e.g. shaman + wow_spec {str} -- wow spec, e.g. elemental + tier {str} -- profile tier, e.g. 21 or PR + + Returns: + str -- relative link to the standard simc profile + """ + + logger.debug("Basic profile string creating started.") + # create the basis profile string + basis_profile_string: str = settings.executable.split("simc")[0] + basis_profile_string += "profiles/" + if tier == "PR": + basis_profile_string += "PreRaids/PR_{}_{}".format( + wow_class.title(), wow_spec.title() + ) + else: + basis_profile_string += "Tier{}/T{}_{}_{}".format( + tier, tier, wow_class, wow_spec + ).title() + basis_profile_string += ".simc" + + logger.debug( + "Created basis_profile_string '{}'.".format(basis_profile_string) + ) + + return basis_profile_string + + +def pretty_timestamp(): + """Returns a pretty time stamp "YYYY-MM-DD HH:MM" + + Returns: + str -- timestamp + """ + # str(datetime.datetime.utcnow())[:-10] should be the same + return datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M") + + +def create_base_json_dict( + data_type: str, wow_class: str, wow_spec: str, fight_style: str +): + """Creates as basic json dictionary. You'll need to add your data into 'data'. Can be extended. + + Arguments: + data_type {str} -- e.g. Races, Trinkets, Azerite Traits (str is used in the title) + wow_class {str} -- [description] + wow_spec {str} -- [description] + fight_style {str} -- [description] + simulation_group {simulation_objects.Simulation_Group} -- [description] + + Returns: + dict -- [description] + """ + + logger.debug("create_base_json_dict start.") + + timestamp = pretty_timestamp() + + return { + "data_type": + "{}".format(data_type.lower().replace(" ", "_")), + "timestamp": + timestamp, + "title": + "{data_type} | {wow_class} {wow_spec} | {fight_style}".format( + data_type=data_type.title(), + wow_class=wow_class.title(), + wow_spec=wow_spec.title(), + fight_style=fight_style.title() + ), + "subtitle": + "UTC {timestamp} | SimC build: {simc_hash_short}" + .format( + timestamp=timestamp, + simc_hash=settings.simc_hash, + simc_hash_short=settings.simc_hash[0:7] + ), + "simc_settings": { + "tier": settings.tier, + "fight_style": fight_style, + "iterations": settings.iterations, + "target_error": settings.target_error, + "ptr": settings.ptr, + "simc_hash": settings.simc_hash, + "class": wow_class, + "spec": wow_spec + }, + "data": {}, + } + + +def race_simulations(specs: List[Tuple[str, str]]) -> None: + """Simulates all available races for all given specs. + + Arguments: + specs {List[Tuple[str, str]]} -- List of all wanted wow_specs + + Returns: + None -- + """ + + logger.debug("Race simulations start.") for fight_style in settings.fight_styles: for wow_class, wow_spec in specs: - races = wow_lib.get_races_of_class(wow_class) - simulation_group = simulation_objects.simulation_group( + races = wow_lib.get_races_for_class(wow_class) + simulation_group = simulation_objects.Simulation_Group( name="race_simulations", threads=settings.threads, profileset_work_threads=settings.profileset_work_threads, @@ -45,30 +154,19 @@ def race_simulations(specs: [(str, str)]): simulation_data = None if race == races[0]: - # create the basis profile string - basis_profile_string = settings.executable.split("simc")[0] - basis_profile_string += "profiles/" - if settings.tier == "PR": - basis_profile_string += "PreRaids/" - else: - basis_profile_string += "Tier{}/".format(settings.tier) - basis_profile_string += "T{}_{}_{}".format( - settings.tier, wow_class, wow_spec - ).title() - basis_profile_string += ".simc" - - logger.debug( - "Created basis_profile_string '{}'.".format(basis_profile_string) + + basic_profile_string = create_basic_profile_string( + wow_class, wow_spec, settings.tier ) - simulation_data = simulation_objects.simulation_data( + simulation_data = simulation_objects.Simulation_Data( name=race, fight_style=fight_style, - simc_arguments=[basis_profile_string, "race={}".format(race)], + simc_arguments=[basic_profile_string, "race={}".format(race)], logger=logger ) else: - simulation_data = simulation_objects.simulation_data( + simulation_data = simulation_objects.Simulation_Data( name=race, fight_style=fight_style, simc_arguments=["race={}".format(race)], @@ -77,25 +175,460 @@ def race_simulations(specs: [(str, str)]): simulation_group.add(simulation_data) logger.debug(( "Added race '{}' in profile '{}' to simulation_group.".format( - simulation_data.name, race + race, simulation_data.name ) )) - logger.debug("Start simulation.") + logger.debug("Start race simulation.") simulation_group.simulate() - logger.debug("Finished simulation.") + logger.debug("Finished race simulation.") for profile in simulation_group.profiles: logger.debug( "Profile '{}' DPS: {}".format(profile.name, profile.get_dps()) ) - logger.debug("Ended") - pass + # save data to json + wanted_data = create_base_json_dict( + "Races", wow_class, wow_spec, fight_style + ) + + logger.debug("Created base dict for json export. {}".format(wanted_data)) + + # add dps values to json + for profile in simulation_group.profiles: + wanted_data["data"][profile.name] = profile.get_dps() + logger.debug( + "Added '{}' with {} dps to json.".format( + profile.name, profile.get_dps() + ) + ) + + logger.debug("Final json: {}".format(wanted_data)) + + # write json to file + with open( + "results/races/races_{}_{}_{}.json".format( + wow_class, wow_spec, fight_style + ), "w" + ) as f: + logger.debug("Print race json.") + f.write(json.dumps(wanted_data, sort_keys=True, indent=4)) + logger.debug("Printed race json.") + + logger.debug("Race simulations ended.") + + +def trinket_simulations(specs: List[Tuple[str, str]]) -> None: + """Simulates all available trinkets to all given wow_classes and wow_specs. + + Arguments: + specs {List[Tuple[str, str]]} -- [description] + + Raises: + NotImplementedError -- [description] + + Returns: + None -- [description] + """ + + logger.debug("trinket_simulations start.") + for fight_style in settings.fight_styles: + simulation_results = {} + for wow_class, wow_spec in specs: + + if not wow_class in simulation_results: + simulation_results[wow_class] = {} + + # get main-trinkets + trinket_list = wow_lib.get_trinkets_for_spec(wow_class, wow_spec) + # get secondary-trinket (standard stat stick) + second_trinket = wow_lib.get_second_trinket_for_spec(wow_class, wow_spec) + + simulation_group = simulation_objects.Simulation_Group( + name="{} {}".format(wow_class.title(), wow_spec.title()), + threads=settings.threads, + profileset_work_threads=settings.profileset_work_threads, + executable=settings.executable, + logger=logger + ) + + for trinket in trinket_list: + + if trinket == trinket_list[0]: + simulation_data = simulation_objects.Simulation_Data( + name="baseline {}".format(settings.min_ilevel), + fight_style=fight_style, + iterations=settings.iterations, + target_error=settings.target_error, + simc_arguments=[ + create_basic_profile_string(wow_class, wow_spec, settings.tier), + "trinket1=", "trinket2=" + second_trinket + ], + logger=logger + ) + simulation_group.add(simulation_data) + + # for each available itemlevel of the trinket + for itemlevel in range( + settings.min_ilevel, settings.max_ilevel, settings.ilevel_step + ): + + if itemlevel >= trinket[2] and itemlevel <= trinket[3]: + + simulation_data = simulation_objects.Simulation_Data( + name="{} {}".format(trinket[0], itemlevel), + fight_style=fight_style, + iterations=settings.iterations, + target_error=settings.target_error, + simc_arguments=[ + "trinket1=,id={},ilevel={}".format(trinket[1], itemlevel) + ], + logger=logger + ) + simulation_group.add(simulation_data) + + # create and simulate baseline profile + logger.debug("Start trinket simulation.") + simulation_group.simulate() + logger.debug("Finished trinket simulation.") + + for profile in simulation_group.profiles: + logger.info( + "{} {} DPS, baseline +{}".format( + profile.name, profile.get_dps(), + profile.get_dps() - simulation_group.get_dps_of( + "baseline {}".format(settings.min_ilevel) + ) + ) + ) + + simulation_results[wow_class][wow_spec] = simulation_group + + # json exporter + json_export = create_base_json_dict( + "trinkets", wow_class, wow_spec, fight_style + ) + + for profile in simulation_group.profiles: + + name = profile.name[:profile.name.rfind(" ")] + ilevel = profile.name[profile.name.rfind(" ") + 1:] + + if not name in json_export["data"]: + json_export["data"][name] = {} + + json_export["data"][name][ilevel] = profile.get_dps() + + # create item_id table + json_export["item_ids"] = {} + for trinket in json_export["data"]: + if trinket != "baseline": + json_export["item_ids"][trinket] = wow_lib.get_trinket_id(trinket) + + logger.debug("Enriched json export: {}".format(json_export)) + + # create ordered trinket name list + tmp_list = [] + for trinket in json_export["data"]: + if trinket != "baseline": + tmp_list.append( + (trinket, max(json_export["data"][trinket].values())) + ) + logger.debug("tmp_list: {}".format(tmp_list)) + + tmp_list = sorted(tmp_list, key=lambda item: item[1], reverse=True) + logger.debug("Sorted tmp_list: {}".format(tmp_list)) + + json_export["sorted_data_keys"] = [] + for trinket, _ in tmp_list: + json_export["sorted_data_keys"].append(trinket) + + # add itemlevel list + json_export["simulated_itemlevels"] = [] + for itemlevel in range( + settings.min_ilevel, settings.max_ilevel, settings.ilevel_step + ): + json_export["simulated_itemlevels"].append(itemlevel) + + # write json to file + with open( + "results/trinkets/trinkets_{}_{}_{}.json".format( + wow_class, wow_spec, fight_style + ), "w" + ) as f: + logger.debug("Print trinket json.") + f.write(json.dumps(json_export, sort_keys=True, indent=4)) + logger.debug("Printed trinket json.") + + # LUA data exporter + if fight_style.lower() == "patchwerk": + human_readable = True + item_dict = { + } # intended structure: itemID -> class -> spec -> itemlevel + + for wow_class in simulation_results: + wow_class_id = wow_class if human_readable else wow_lib.get_class_id( + wow_class + ) + + for wow_spec in simulation_results[wow_class]: + wow_spec_id = wow_spec if human_readable else wow_lib.get_spec_id( + wow_class, wow_spec + ) + + for profile in simulation_results[wow_class][wow_spec].profiles: + if profile.name != "baseline {}".format(settings.min_ilevel): + name = profile.name[:profile.name.rfind(" ")] + ilevel = profile.name[profile.name.rfind(" ") + 1:] + + item_id = name if human_readable else wow_lib.get_trinket_id( + name + ) + + if not item_id in item_dict: + item_dict[item_id] = {} + + if not wow_class_id in item_dict[item_id]: + item_dict[item_id][wow_class_id] = {} + + if not wow_spec_id in item_dict[item_id][wow_class_id]: + item_dict[item_id][wow_class_id][wow_spec_id] = {} + + item_dict[item_id + ][wow_class_id][wow_spec_id][ilevel] = profile.get_dps( + ) - simulation_results[wow_class][wow_spec].get_dps_of( + "baseline {}".format(settings.min_ilevel) + ) + + logger.debug("item_dict: {}".format(item_dict)) + + # TODO enhance item_dict with missing itemlevels + #for itemlevel in range(settings.min_ilevel, settings.max_ilevel, 5): + + with open("results/trinkets/ItemDPS.lua", "w") as f: + logger.debug("Print trinket lua.") + f.write("-- itemID -> class -> spec -> itemlevel\n") + f.write("MoreItemInfo.Enum.ItemDPS = {\n") + + for trinket in item_dict: + f.write("{}[{}] = {{\n".format(" " * 4, trinket)) + + for wow_class in item_dict[trinket]: + f.write("{}[{}] = {{\n".format(" " * 8, wow_class)) + + for wow_spec in item_dict[trinket][wow_class]: + f.write("{}[{}] = {{\n".format(" " * 12, wow_spec)) + + for itemlevel in item_dict[trinket][wow_class][wow_spec]: + # TODO might need a transformer for the itemlevel here + f.write( + "{}[{}] = {}".format( + " " * 16, itemlevel, + item_dict[trinket][wow_class][wow_spec][itemlevel] + ) + ) + + if itemlevel == list( + item_dict[trinket][wow_class][wow_spec].keys() + )[-1]: + f.write("\n") + else: + f.write(",\n") + + if wow_spec == list(item_dict[trinket][wow_class].keys())[-1]: + f.write("{}}}\n".format(" " * 12)) + else: + f.write("{}}},\n".format(" " * 12)) + + if wow_class == list(item_dict[trinket].keys())[-1]: + f.write("{}}}\n".format(" " * 8)) + else: + f.write("{}}},\n".format(" " * 8)) + + if trinket == list(item_dict.keys())[-1]: + f.write("{}}}\n".format(" " * 4)) + else: + f.write("{}}},\n".format(" " * 4)) + f.write("}\n") + logger.debug("Printed trinket lua.") + + logger.debug("Trinket simulations ended.") + + +def find_secondary_distributions( + wow_class: str, + wow_spec: str, + talent_combinations: List[str], +) -> List[simulation_objects.Simulation_Group]: + + distribution_multipliers = [] + step_size = 10 + lower_border = 10 + upper_border = 70 + secondary_amount = 0 + + # get secondary sum from profile + with open( + create_basic_profile_string(wow_class, wow_spec, settings.tier), 'r' + ) as f: + for line in f: + if "gear_crit_rating=" in line: + secondary_amount += int(line.split("=")[1]) + elif "gear_haste_rating=" in line: + secondary_amount += int(line.split("=")[1]) + elif "gear_mastery_rating=" in line: + secondary_amount += int(line.split("=")[1]) + elif "gear_versatility_rating=" in line: + secondary_amount += int(line.split("=")[1]) + logger.debug("secondary_amount found: {}".format(secondary_amount)) + + # generate all valied secondary distributions + for c in range(lower_border, upper_border + step_size, step_size): + for h in range(lower_border, upper_border + step_size, step_size): + for m in range(lower_border, upper_border + step_size, step_size): + for v in range(lower_border, upper_border + step_size, step_size): + if c + h + m + v == 100: + distribution_multipliers.append((c, h, m, v)) + + logger.debug( + "'{}' different distributions generated.".format( + len(distribution_multipliers) + ) + ) + + for fight_style in settings.fight_styles: + + result_dict = create_base_json_dict( + "secondary_distributions", wow_class, wow_spec, fight_style + ) + result_dict["sorted_data_keys"] = {} + for talent_combination in talent_combinations: + simulation_group = simulation_objects.Simulation_Group( + name=talent_combination, + threads=settings.threads, + profileset_work_threads=settings.profileset_work_threads, + executable=settings.executable, + logger=logger + ) + for distribution_multiplier in distribution_multipliers: + # if it is the first one + if distribution_multiplier == distribution_multipliers[0]: + simulation_group.add( + simulation_objects.Simulation_Data( + name="{}_{}_{}_{}".format( + distribution_multiplier[0], distribution_multiplier[1], + distribution_multiplier[2], distribution_multiplier[3] + ), + fight_style=fight_style, + target_error=settings.target_error, + iterations=settings.iterations, + logger=logger, + simc_arguments=[ + create_basic_profile_string( + wow_class, wow_spec, settings.tier + ), + "talents={}".format(talent_combination), + "gear_crit_rating={}".format( + secondary_amount * (distribution_multiplier[0] / 100) + ), + "gear_haste_rating={}".format( + secondary_amount * (distribution_multiplier[1] / 100) + ), + "gear_mastery_rating={}".format( + secondary_amount * (distribution_multiplier[2] / 100) + ), + "gear_versatility_rating={}".format( + secondary_amount * (distribution_multiplier[3] / 100) + ), + ] + ) + ) + else: + simulation_group.add( + simulation_objects.Simulation_Data( + name="{}_{}_{}_{}".format( + distribution_multiplier[0], distribution_multiplier[1], + distribution_multiplier[2], distribution_multiplier[3] + ), + fight_style=fight_style, + target_error=settings.target_error, + iterations=settings.iterations, + logger=logger, + simc_arguments=[ + "gear_crit_rating={}".format( + secondary_amount * (distribution_multiplier[0] / 100) + ), + "gear_haste_rating={}".format( + secondary_amount * (distribution_multiplier[1] / 100) + ), + "gear_mastery_rating={}".format( + secondary_amount * (distribution_multiplier[2] / 100) + ), + "gear_versatility_rating={}".format( + secondary_amount * (distribution_multiplier[3] / 100) + ), + ] + ) + ) + + logger.info("Start secondary distribution simulation.") + simulation_group.simulate() + logger.info("Finished secondary distribution simulation.") + + if settings.debug: + logger.debug("Talent combination: " + talent_combination) + for profile in simulation_group.profiles: + logger.debug("{} {}".format(profile.name, profile.get_dps())) + + stat_dps_list = [] + result_dict["data"][talent_combination] = {} + + for profile in simulation_group.profiles: + stat_dps_list.append((profile.name, profile.get_dps())) + result_dict["data"][talent_combination][profile.name + ] = profile.get_dps() + + stat_dps_list = sorted( + stat_dps_list, key=lambda item: item[1], reverse=True + ) + logger.debug( + "Sorted secondary distribution list for {}:". + format(talent_combination) + ) + logger.debug("c h m v dps") + for item in stat_dps_list: + logger.debug( + "{} {} {}%".format( + item[0], item[1], round(item[1] * 100 / stat_dps_list[0][1], 2) + ) + ) + result_dict["sorted_data_keys"][talent_combination] = [] + for item in stat_dps_list: + result_dict["sorted_data_keys"][talent_combination].append(item[0]) + + logger.debug(result_dict) + with open( + "results/secondary_distributions/{}_{}_{}.json".format( + wow_class.lower(), wow_spec.lower(), fight_style.lower() + ), 'w' + ) as f: + logger.debug("Print secondary distribution json.") + f.write(json.dumps(result_dict, sort_keys=True, indent=4)) + logger.debug("Printed secondary distribution json.") def main(): - logger.debug("Started") - race_simulations([("shaman", "elemental")]) - logger.debug("Ended") + logger.debug("main started") + # trigger race simulations for elemental shamans + #race_simulations([("shaman", "elemental")]) + # trigger trinket simulations for elemental shamans + # trinket_simulations([ + # #("rogue", "assassination"), + # #("rogue", "outlaw"), + # ("shaman", "elemental") + # ]) + + find_secondary_distributions("shaman", "elemental", ["1111111"]) + logger.debug("main ended") if __name__ == '__main__': diff --git a/bloodytools/settings.py b/bloodytools/settings.py index d9e21ca..dac370c 100644 --- a/bloodytools/settings.py +++ b/bloodytools/settings.py @@ -1,11 +1,24 @@ # Profile setttings -tier = "21" # number or PR (PreRaid) -fight_styles = ["patchwerk", "beastlord"] +tier = "PR" # number or PR (PreRaid) +fight_styles = [ + "patchwerk", + #"beastlord" +] # Simulation settings -executable = "../../SimulationCraft/simc.exe" +executable = "../../SimulationCraft_BfA/simc.exe" threads = "8" profileset_work_threads = "2" +target_error = "0.1" +iterations = "25000" +ptr = "1" # General Bloodytools Settings - you usually don't need to touch these debug = True +simc_hash = "129d90531685e1e9388127317cabc57b0e5668d2" + +# trinkets +min_ilevel = 300 # min_ilevel is used to determine the first simulated itemlevel +max_ilevel = 355 # max_itemlevel determines the upper border of steps taken +ilevel_step = 10 # ilevel_step is used to determine the size of each itemlevel step taken to max_ilevel +# example: min 300, max 325, step 10, resulting simulated ilevels: 300, 310, 320 diff --git a/bloodytools/simulation_objects/simulation_objects.py b/bloodytools/simulation_objects/simulation_objects.py index 1d71063..3d700cd 100644 --- a/bloodytools/simulation_objects/simulation_objects.py +++ b/bloodytools/simulation_objects/simulation_objects.py @@ -59,7 +59,7 @@ class SimulationError(Error): pass -class simulation_data(): +class Simulation_Data(): """Manages all META-information for a single simulation and the result. TODO: add max_time, vary_combat_length @@ -87,7 +87,7 @@ def __init__( logger: logging.Logger = None ) -> None: - super(simulation_data, self).__init__() + super(Simulation_Data, self).__init__() self.logger = logger or logging.getLogger(__name__) self.logger.debug("simulation_data initiated.") @@ -112,12 +112,12 @@ def __init__( # if no value was set, determine a standard value if executable == None: if sys.platform == 'win32': - self.logger.info( + self.logger.debug( "Setting Windows default value for executable. This might not work for your system." ) self.executable = "../simc.exe" else: - self.logger.info( + self.logger.debug( "Setting Linux default value for executable. This might not work for your system." ) self.executable = "../simc" @@ -165,8 +165,8 @@ def __init__( else: self.optimize_expressions = "1" # simc setting to enable/disable ptr data - if ptr == "0" or ptr == "1": - self.ptr = ptr + if str(ptr) == "0" or str(ptr) == "1": + self.ptr = str(ptr) else: self.ptr = "0" # simc setting to enable/disable ready_trigger @@ -471,13 +471,13 @@ def simulate(self) -> int: return self.get_dps() -class simulation_group(): +class Simulation_Group(): """simulator_group holds one or multiple simulation_data as profiles and can simulate them either serialized or parallel. Parallel uses SimulationCrafts own profilesets feature. Dps values are saved in the simulation_data. """ def __init__( self, - simulation_instance: Union[simulation_data, List[simulation_data]] = None, + simulation_instance: Union[Simulation_Data, List[Simulation_Data]] = None, name: str = "", threads: str = "", profileset_work_threads: str = "", @@ -493,7 +493,7 @@ def __init__( # simulationcrafts own multithreading self.profileset_work_threads = profileset_work_threads self.executable = executable - self.profiles: List[simulation_data] + self.profiles: List[Simulation_Data] self.sg_simulation_start_time: datetime.datetime = None self.sg_simulation_end_time: datetime.datetime = None @@ -503,7 +503,7 @@ def __init__( elif type(simulation_instance) == list: correct_type = True for data in simulation_instance: # type: ignore - if type(data) != simulation_data: + if type(data) != Simulation_Data: correct_type = False if correct_type: self.profiles = simulation_instance # type: ignore @@ -511,7 +511,7 @@ def __init__( raise TypeError( "At least one item of simulation_instance list had a wrong type. Expected simulation_data." ) - elif type(simulation_instance) == simulation_data: + elif type(simulation_instance) == Simulation_Data: self.profiles = [simulation_instance] # type: ignore else: raise TypeError( @@ -642,7 +642,9 @@ def simulate(self) -> bool: if profile == self.profiles[0]: for argument in profile.simc_arguments: f.write("{}\n".format(argument)) - f.write("name={}\n\n# Profileset start\n".format(profile.name)) + f.write( + "name=\"{}\"\n\n# Profileset start\n".format(profile.name) + ) # or else in wrong scope f.write( "ready_trigger={}\n".format(self.profiles[0].ready_trigger) @@ -719,8 +721,9 @@ def simulate(self) -> bool: for line in simulation_output.stdout.splitlines(): # needs this check to prevent grabbing the boss dps - if "DPS:" in line and is_actor: - self.profiles[0].set_dps(line.split()[1], external=False) + if self.profiles[0].name in line and is_actor: + self.logger.debug(line) + self.profiles[0].set_dps(line.split()[0], external=False) is_actor = False if "Profilesets (median Damage per Second):" in line: @@ -729,7 +732,8 @@ def simulate(self) -> bool: # get dps values of the profileset-simulations if profileset_results: for profile in self.profiles: - if profile.name in line: + # need this space to catch the difference of multiple profiles like 'tauren' and 'highmountain_tauren' + if " " + profile.name in line: profile.set_dps(line.split()[0], external=False) # prevents false positive from Waiting -data @@ -751,7 +755,7 @@ def simulate(self) -> bool: return True - def add(self, simulation_instance: simulation_data) -> bool: + def add(self, simulation_instance: Simulation_Data) -> bool: """Add another simulation_instance object to the group. Arguments: @@ -772,7 +776,7 @@ def add(self, simulation_instance: simulation_data) -> bool: @return True, if adding data was successfull. Exception otherwise. """ - if type(simulation_instance) == simulation_data: + if type(simulation_instance) == Simulation_Data: try: self.profiles.append(simulation_instance) except Exception as e: @@ -784,3 +788,18 @@ def add(self, simulation_instance: simulation_data) -> bool: "Simulation_instance has wrong type '{}' (needed simulation_data).". format(type(simulation_instance)) ) + + def get_dps_of(self, profile_name: str) -> int: + """Returns DPS of the wanted/named profile. + + Arguments: + profile_name {str} -- Name of the profile. e.g. 'baseline' + + Returns: + int -- dps + """ + + for profile in self.profiles: + if profile.name == profile_name: + return profile.get_dps() + return None diff --git a/bloodytools/simulation_objects/simulation_objects_tests.py b/bloodytools/simulation_objects/simulation_objects_tests.py index 1eb22c0..d3b4640 100644 --- a/bloodytools/simulation_objects/simulation_objects_tests.py +++ b/bloodytools/simulation_objects/simulation_objects_tests.py @@ -27,7 +27,7 @@ def test_empty(self): """Test all available and left empty input values for their correct default values. """ - self.sd = simulation_objects.simulation_data() + self.sd = simulation_objects.Simulation_Data() self.assertEqual(self.sd.calculate_scale_factors, "0") self.assertEqual(self.sd.default_actions, "1") self.assertEqual(self.sd.default_skill, "1.0") @@ -53,7 +53,7 @@ def test_calculate_scale_factors(self): """Test input checks of calculate_scale_factors. """ - self.sd = simulation_objects.simulation_data(calculate_scale_factors="1") + self.sd = simulation_objects.Simulation_Data(calculate_scale_factors="1") self.assertEqual(self.sd.calculate_scale_factors, "1") self.assertNotEqual(self.sd.calculate_scale_factors, "0") self.assertNotEqual(self.sd.calculate_scale_factors, str) @@ -69,7 +69,7 @@ def test_calculate_scale_factors(self): ## @return { description_of_the_return_value } ## def test_default_actions(self): - self.sd = simulation_objects.simulation_data(default_actions="1") + self.sd = simulation_objects.Simulation_Data(default_actions="1") self.assertEqual(self.sd.default_actions, "1") self.assertNotEqual(self.sd.default_actions, "0") self.assertNotEqual(self.sd.default_actions, str) @@ -78,7 +78,7 @@ def test_default_actions(self): self.assertNotEqual(self.sd.default_actions, int) def test_fight_style(self): - self.sd = simulation_objects.simulation_data(fight_style="helterskelter") + self.sd = simulation_objects.Simulation_Data(fight_style="helterskelter") self.assertEqual(self.sd.fight_style, "helterskelter") self.assertNotEqual(self.sd.fight_style, "0") self.assertNotEqual(self.sd.fight_style, str) @@ -87,11 +87,11 @@ def test_fight_style(self): self.assertNotEqual(self.sd.fight_style, int) # simc_checks needs improvements to catch this #self.sd = None - #self.sd = simulation_objects.simulation_data( fight_style=1234 ) + #self.sd = simulation_objects.Simulation_Data( fight_style=1234 ) #self.assertEqual( self.sd.fight_style, "patchwerk" ) def test_fixed_time(self): - self.sd = simulation_objects.simulation_data(fixed_time="1") + self.sd = simulation_objects.Simulation_Data(fixed_time="1") self.assertEqual(self.sd.fixed_time, "1") self.assertNotEqual(self.sd.fixed_time, "0") self.assertNotEqual(self.sd.fixed_time, str) @@ -100,7 +100,7 @@ def test_fixed_time(self): self.assertNotEqual(self.sd.fixed_time, int) def test_html(self): - self.sd = simulation_objects.simulation_data(html="testiger.html") + self.sd = simulation_objects.Simulation_Data(html="testiger.html") self.assertEqual(self.sd.html, "testiger.html") self.assertNotEqual(self.sd.html, "") self.assertNotEqual(self.sd.html, str) @@ -109,7 +109,7 @@ def test_html(self): self.assertNotEqual(self.sd.html, int) def test_iterations(self): - self.sd = simulation_objects.simulation_data(iterations="15000") + self.sd = simulation_objects.Simulation_Data(iterations="15000") self.assertEqual(self.sd.iterations, "15000") self.assertNotEqual(self.sd.iterations, "") self.assertNotEqual(self.sd.iterations, str) @@ -117,14 +117,14 @@ def test_iterations(self): self.assertNotEqual(self.sd.iterations, dict) self.assertNotEqual(self.sd.iterations, int) self.sd = None - self.sd = simulation_objects.simulation_data(iterations=15000) + self.sd = simulation_objects.Simulation_Data(iterations=15000) self.assertEqual(self.sd.iterations, "15000") self.sd = None - self.sd = simulation_objects.simulation_data(iterations=15000.75) + self.sd = simulation_objects.Simulation_Data(iterations=15000.75) self.assertEqual(self.sd.iterations, "15000") def test_log(self): - self.sd = simulation_objects.simulation_data(log="1") + self.sd = simulation_objects.Simulation_Data(log="1") self.assertEqual(self.sd.log, "1") self.assertNotEqual(self.sd.log, "") self.assertNotEqual(self.sd.log, str) @@ -132,19 +132,19 @@ def test_log(self): self.assertNotEqual(self.sd.log, dict) self.assertNotEqual(self.sd.log, int) self.sd = None - self.sd = simulation_objects.simulation_data(iterations=15000) + self.sd = simulation_objects.Simulation_Data(iterations=15000) self.assertEqual(self.sd.log, "0") def test_name(self): - self.sd = simulation_objects.simulation_data(name="") + self.sd = simulation_objects.Simulation_Data(name="") self.assertNotEqual(self.sd.name, "") self.assertEqual(type(self.sd.name), str) self.sd = None - self.sd = simulation_objects.simulation_data(name="Borke") + self.sd = simulation_objects.Simulation_Data(name="Borke") self.assertEqual(self.sd.name, "Borke") def test_optimize_expressions(self): - self.sd = simulation_objects.simulation_data(optimize_expressions="1") + self.sd = simulation_objects.Simulation_Data(optimize_expressions="1") self.assertEqual(self.sd.optimize_expressions, "1") self.assertNotEqual(self.sd.optimize_expressions, "0") self.assertNotEqual(self.sd.optimize_expressions, str) @@ -152,11 +152,11 @@ def test_optimize_expressions(self): self.assertNotEqual(self.sd.optimize_expressions, dict) self.assertNotEqual(self.sd.optimize_expressions, int) self.sd = None - self.sd = simulation_objects.simulation_data(optimize_expressions=["1"]) + self.sd = simulation_objects.Simulation_Data(optimize_expressions=["1"]) self.assertEqual(self.sd.optimize_expressions, "1") def test_ptr(self): - self.sd = simulation_objects.simulation_data(ptr="1") + self.sd = simulation_objects.Simulation_Data(ptr="1") self.assertEqual(self.sd.ptr, "1") self.assertNotEqual(self.sd.ptr, "0") self.assertNotEqual(self.sd.ptr, str) @@ -164,11 +164,11 @@ def test_ptr(self): self.assertNotEqual(self.sd.ptr, dict) self.assertNotEqual(self.sd.ptr, int) self.sd = None - self.sd = simulation_objects.simulation_data(ptr=["1"]) + self.sd = simulation_objects.Simulation_Data(ptr=["1"]) self.assertEqual(self.sd.ptr, "0") def test_ready_trigger(self): - self.sd = simulation_objects.simulation_data(ready_trigger="1") + self.sd = simulation_objects.Simulation_Data(ready_trigger="1") self.assertEqual(self.sd.ready_trigger, "1") self.assertNotEqual(self.sd.ready_trigger, "0") self.assertNotEqual(self.sd.ready_trigger, str) @@ -176,31 +176,31 @@ def test_ready_trigger(self): self.assertNotEqual(self.sd.ready_trigger, dict) self.assertNotEqual(self.sd.ready_trigger, int) self.sd = None - self.sd = simulation_objects.simulation_data(ready_trigger=["1"]) + self.sd = simulation_objects.Simulation_Data(ready_trigger=["1"]) self.assertEqual(self.sd.ready_trigger, "1") def test_simc_arguments(self): - self.sd = simulation_objects.simulation_data(simc_arguments="1") + self.sd = simulation_objects.Simulation_Data(simc_arguments="1") self.assertNotEqual(self.sd.simc_arguments, "1") self.assertEqual(self.sd.simc_arguments, ["1"]) self.sd = None - self.sd = simulation_objects.simulation_data(simc_arguments=["a", "b"]) + self.sd = simulation_objects.Simulation_Data(simc_arguments=["a", "b"]) self.assertEqual(self.sd.simc_arguments, ["a", "b"]) def test_target_error(self): - self.sd = simulation_objects.simulation_data(target_error="0.5") + self.sd = simulation_objects.Simulation_Data(target_error="0.5") self.assertEqual(self.sd.target_error, "0.5") self.assertNotEqual(self.sd.target_error, ["1"]) self.sd = None - self.sd = simulation_objects.simulation_data(target_error=["a", "b"]) + self.sd = simulation_objects.Simulation_Data(target_error=["a", "b"]) self.assertEqual(self.sd.target_error, "0.1") def test_threads(self): - self.sd = simulation_objects.simulation_data(threads="1.5") + self.sd = simulation_objects.Simulation_Data(threads="1.5") self.assertEqual(self.sd.threads, "1") self.assertNotEqual(self.sd.threads, "1.5") self.sd = None - self.sd = simulation_objects.simulation_data(threads=["a", "b"]) + self.sd = simulation_objects.Simulation_Data(threads=["a", "b"]) self.assertEqual(self.sd.threads, "") @@ -210,53 +210,53 @@ def test_threads(self): class TestSimulationDataMethods(unittest.TestCase): def setUp(self): - self.sd = simulation_objects.simulation_data() + self.sd = simulation_objects.Simulation_Data() def tearDown(self): self.sd = None def test_is_equal(self): sd1 = self.sd - sd2 = simulation_objects.simulation_data() + sd2 = simulation_objects.Simulation_Data() self.assertTrue(sd1.is_equal(sd2)) self.assertTrue(sd2.is_equal(sd1)) self.assertTrue(sd1.is_equal(sd1)) self.assertFalse(sd1.is_equal("Döner")) - sd_calculate_scale_factors = simulation_objects.simulation_data( + sd_calculate_scale_factors = simulation_objects.Simulation_Data( calculate_scale_factors="1" ) self.assertFalse(sd_calculate_scale_factors.is_equal(sd1)) - sd_default_actions = simulation_objects.simulation_data( + sd_default_actions = simulation_objects.Simulation_Data( default_actions="0" ) self.assertFalse(sd_default_actions.is_equal(sd1)) - sd_default_skill = simulation_objects.simulation_data(default_skill="0.5") + sd_default_skill = simulation_objects.Simulation_Data(default_skill="0.5") self.assertFalse(sd_default_skill.is_equal(sd1)) - sd_executable = simulation_objects.simulation_data(executable="1") + sd_executable = simulation_objects.Simulation_Data(executable="1") self.assertFalse(sd_executable.is_equal(sd1)) - sd_fight_style = simulation_objects.simulation_data( + sd_fight_style = simulation_objects.Simulation_Data( fight_style="helterskelter" ) self.assertFalse(sd_fight_style.is_equal(sd1)) - sd_fixed_time = simulation_objects.simulation_data(fixed_time="0") + sd_fixed_time = simulation_objects.Simulation_Data(fixed_time="0") self.assertFalse(sd_fixed_time.is_equal(sd1)) - sd_html = simulation_objects.simulation_data(html="test.html") + sd_html = simulation_objects.Simulation_Data(html="test.html") self.assertFalse(sd_html.is_equal(sd1)) - sd_iterations = simulation_objects.simulation_data(iterations="124153") + sd_iterations = simulation_objects.Simulation_Data(iterations="124153") self.assertFalse(sd_iterations.is_equal(sd1)) - sd_log = simulation_objects.simulation_data(log="1") + sd_log = simulation_objects.Simulation_Data(log="1") self.assertFalse(sd_log.is_equal(sd1)) - sd_optimize_expressions = simulation_objects.simulation_data( + sd_optimize_expressions = simulation_objects.Simulation_Data( optimize_expressions="0" ) self.assertFalse(sd_optimize_expressions.is_equal(sd1)) - sd_ptr = simulation_objects.simulation_data(ptr="1") + sd_ptr = simulation_objects.Simulation_Data(ptr="1") self.assertFalse(sd_ptr.is_equal(sd1)) - sd_ready_trigger = simulation_objects.simulation_data(ready_trigger="0") + sd_ready_trigger = simulation_objects.Simulation_Data(ready_trigger="0") self.assertFalse(sd_ready_trigger.is_equal(sd1)) - sd_target_error = simulation_objects.simulation_data(target_error="0.5") + sd_target_error = simulation_objects.Simulation_Data(target_error="0.5") self.assertFalse(sd_target_error.is_equal(sd1)) - sd_threads = simulation_objects.simulation_data(threads="4") + sd_threads = simulation_objects.Simulation_Data(threads="4") self.assertFalse(sd_threads.is_equal(sd1)) def test_get_dps(self): @@ -272,7 +272,7 @@ def test_set_dps(self): with self.assertRaises(TypeError): self.sd.set_dps("5678", external="Hallo") self.sd = None - self.sd = simulation_objects.simulation_data() + self.sd = simulation_objects.Simulation_Data() with self.assertRaises(TypeError): self.sd.set_dps("Bonjour", external="Bonsoir") self.assertEqual(self.sd.get_dps(), None) @@ -281,11 +281,11 @@ def test_set_dps(self): self.assertEqual(self.sd.get_dps(), None) def test_get_avg(self): - sd = simulation_objects.simulation_data() + sd = simulation_objects.Simulation_Data() sd.set_dps(100) self.sd.set_dps(50) self.assertEqual(sd.get_avg(self.sd), 75) - sd_empty = simulation_objects.simulation_data() + sd_empty = simulation_objects.Simulation_Data() self.assertEqual(self.sd.get_avg(sd_empty), None) def test_get_simulation_duration(self): @@ -377,38 +377,40 @@ def test_simulate_fail(self): class TestSimulationGroupDataInit(unittest.TestCase): def setUp(self): - self.sd1 = simulation_objects.simulation_data() - self.sd2 = simulation_objects.simulation_data() + self.sd1 = simulation_objects.Simulation_Data() + self.sd2 = simulation_objects.Simulation_Data() def tearDown(self): self.sd1 = None self.sd2 = None def test_empty(self): - with self.assertRaises(TypeError): - simulation_objects.simulation_group() + self.assertEqual( + type(simulation_objects.Simulation_Group()), + simulation_objects.simulation_group + ) def test_correct_list_input(self): - test = simulation_objects.simulation_group([self.sd1, self.sd2]) + test = simulation_objects.Simulation_Group([self.sd1, self.sd2]) self.assertEqual(test.profiles[0], self.sd1) self.assertEqual(test.profiles[1], self.sd2) def test_correct_single_input(self): - test = simulation_objects.simulation_group(self.sd1) + test = simulation_objects.Simulation_Group(self.sd1) self.assertEqual(test.profiles[0], self.sd1) def test_wrong_list_input(self): - tmp = simulation_objects.simulation_data(iterations="5000") + tmp = simulation_objects.Simulation_Data(iterations="5000") with self.assertRaises(ValueError): - simulation_objects.simulation_group([self.sd1, tmp]) + simulation_objects.Simulation_Group([self.sd1, tmp]) with self.assertRaises(TypeError): - simulation_objects.simulation_group([ + simulation_objects.Simulation_Group([ "why", "would", "anyone", "do", "this" ]) def test_wrong_input_type(self): with self.assertRaises(TypeError): - simulation_objects.simulation_group((self.sd1, self.sd2)) + simulation_objects.Simulation_Group((self.sd1, self.sd2)) ## @@ -417,13 +419,13 @@ def test_wrong_input_type(self): class TestSimulationGroupMethods(unittest.TestCase): def setUp(self): - self.sd1 = simulation_objects.simulation_data( + self.sd1 = simulation_objects.Simulation_Data( target_error="1.0", simc_arguments=["talents=2222222"] ) - self.sd2 = simulation_objects.simulation_data( + self.sd2 = simulation_objects.Simulation_Data( target_error=1.0, simc_arguments=["talents=1111111"] ) - self.sg = simulation_objects.simulation_group([self.sd1, self.sd2]) + self.sg = simulation_objects.Simulation_Group([self.sd1, self.sd2]) def test_selfcheck(self): self.assertTrue(self.sg.selfcheck()) @@ -432,7 +434,7 @@ def test_selfcheck(self): def test_add(self): start = len(self.sg.profiles) - new_data = simulation_objects.simulation_data(target_error=1.0) + new_data = simulation_objects.Simulation_Data(target_error=1.0) self.assertTrue(self.sg.add(new_data)) self.assertEqual(len(self.sg.profiles), start + 1) with self.assertRaises(TypeError): @@ -440,7 +442,7 @@ def test_add(self): def test_simulate_single_data(self): self.sd1.executable = "Not_a_correct_value" - sole_sg1 = simulation_objects.simulation_group(self.sd1) + sole_sg1 = simulation_objects.Simulation_Group(self.sd1) with self.assertRaises(FileNotFoundError): sole_sg1.simulate() @@ -467,7 +469,7 @@ def test_simulate_single_data(self): self.sd2.simc_arguments.insert( 0, "../SimulationCraft/profiles/Tier21/T21_Shaman_Elemental.simc" ) - sole_sg2 = simulation_objects.simulation_group(self.sd2) + sole_sg2 = simulation_objects.Simulation_Group(self.sd2) self.assertTrue(sole_sg2.simulate()) self.sg.profiles = None self.assertFalse(self.sg.simulate()) From 3eba85ca94c9889928da12fae233d7a3a6cf8742 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Tue, 15 May 2018 00:58:10 +0200 Subject: [PATCH 58/65] log error and skip broken / not found profiles, new output file naming scheme (leave out the doubled data type), sim all the specs (try it) --- .gitignore | 7 + bloodytools/bloodytools.py | 186 +++++++++++++----- bloodytools/settings.py | 8 +- .../simulation_objects/simulation_objects.py | 8 +- requirements.txt | 2 +- 5 files changed, 158 insertions(+), 53 deletions(-) diff --git a/.gitignore b/.gitignore index 80b443c..fa801a8 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,10 @@ # local type checks /.mypy_cache + +/bloodytools/results/races +/bloodytools/results/trinkets +/bloodytools/results/traits +/bloodytools/results/secondary_distributions +*.simc +log.txt diff --git a/bloodytools/bloodytools.py b/bloodytools/bloodytools.py index ce9762f..a173276 100644 --- a/bloodytools/bloodytools.py +++ b/bloodytools/bloodytools.py @@ -43,7 +43,7 @@ def create_basic_profile_string(wow_class: str, wow_spec: str, tier: str): str -- relative link to the standard simc profile """ - logger.debug("Basic profile string creating started.") + logger.debug("create_basic_profile_string start") # create the basis profile string basis_profile_string: str = settings.executable.split("simc")[0] basis_profile_string += "profiles/" @@ -60,7 +60,7 @@ def create_basic_profile_string(wow_class: str, wow_spec: str, tier: str): logger.debug( "Created basis_profile_string '{}'.".format(basis_profile_string) ) - + logger.debug("create_basic_profile_string ended") return basis_profile_string @@ -90,7 +90,7 @@ def create_base_json_dict( dict -- [description] """ - logger.debug("create_base_json_dict start.") + logger.debug("create_base_json_dict start") timestamp = pretty_timestamp() @@ -137,9 +137,24 @@ def race_simulations(specs: List[Tuple[str, str]]) -> None: None -- """ - logger.debug("Race simulations start.") + logger.debug("race_simulations start") for fight_style in settings.fight_styles: for wow_class, wow_spec in specs: + + # check whether the baseline profile does exist + try: + with open( + create_basic_profile_string(wow_class, wow_spec, settings.tier), 'r' + ) as f: + pass + except Exception as e: + logger.error( + "Opening baseline profile {} {} failed. This spec won't be in the result. {}". + format(wow_class, wow_spec, e) + ) + # end this try early, no profile, no calculations + continue + races = wow_lib.get_races_for_class(wow_class) simulation_group = simulation_objects.Simulation_Group( name="race_simulations", @@ -178,9 +193,22 @@ def race_simulations(specs: List[Tuple[str, str]]) -> None: race, simulation_data.name ) )) - logger.debug("Start race simulation.") - simulation_group.simulate() - logger.debug("Finished race simulation.") + + logger.debug( + "Start race simulation for {} {}.".format(wow_class, wow_spec) + ) + try: + simulation_group.simulate() + except Exception as e: + logger.error( + "Race simulation for {} {} failed. {}".format( + wow_class, wow_spec, e + ) + ) + continue + else: + logger.debug("Finished race simulation.") + for profile in simulation_group.profiles: logger.debug( "Profile '{}' DPS: {}".format(profile.name, profile.get_dps()) @@ -206,15 +234,15 @@ def race_simulations(specs: List[Tuple[str, str]]) -> None: # write json to file with open( - "results/races/races_{}_{}_{}.json".format( - wow_class, wow_spec, fight_style + "results/races/{}_{}_{}.json".format( + wow_class.lower(), wow_spec.lower(), fight_style.lower() ), "w" ) as f: logger.debug("Print race json.") f.write(json.dumps(wanted_data, sort_keys=True, indent=4)) logger.debug("Printed race json.") - logger.debug("Race simulations ended.") + logger.debug("race_simulations ended") def trinket_simulations(specs: List[Tuple[str, str]]) -> None: @@ -230,11 +258,25 @@ def trinket_simulations(specs: List[Tuple[str, str]]) -> None: None -- [description] """ - logger.debug("trinket_simulations start.") + logger.debug("trinket_simulations start") for fight_style in settings.fight_styles: simulation_results = {} for wow_class, wow_spec in specs: + # check whether the baseline profile does exist + try: + with open( + create_basic_profile_string(wow_class, wow_spec, settings.tier), 'r' + ) as f: + pass + except Exception as e: + logger.error( + "Opening baseline profile {} {} failed. This spec won't be in the result. {}". + format(wow_class, wow_spec, e) + ) + # end this try early, no profile, no calculations + continue + if not wow_class in simulation_results: simulation_results[wow_class] = {} @@ -287,9 +329,20 @@ def trinket_simulations(specs: List[Tuple[str, str]]) -> None: simulation_group.add(simulation_data) # create and simulate baseline profile - logger.debug("Start trinket simulation.") - simulation_group.simulate() - logger.debug("Finished trinket simulation.") + logger.debug( + "Start trinket simulation for {} {}.".format(wow_class, wow_spec) + ) + try: + simulation_group.simulate() + except Exception as e: + logger.error( + "Trinket simulation for {} {} failed. {}".format( + wow_class, wow_spec, e + ) + ) + continue + else: + logger.debug("Finished trinket simulation.") for profile in simulation_group.profiles: logger.info( @@ -351,8 +404,8 @@ def trinket_simulations(specs: List[Tuple[str, str]]) -> None: # write json to file with open( - "results/trinkets/trinkets_{}_{}_{}.json".format( - wow_class, wow_spec, fight_style + "results/trinkets/{}_{}_{}.json".format( + wow_class.lower(), wow_spec.lower(), fight_style.lower() ), "w" ) as f: logger.debug("Print trinket json.") @@ -451,15 +504,17 @@ def trinket_simulations(specs: List[Tuple[str, str]]) -> None: f.write("}\n") logger.debug("Printed trinket lua.") - logger.debug("Trinket simulations ended.") + logger.debug("trinket_simulations ended") -def find_secondary_distributions( +def secondary_distribution_simulations( wow_class: str, wow_spec: str, talent_combinations: List[str], ) -> List[simulation_objects.Simulation_Group]: + logger.debug("secondary_distribution_simulations start") + distribution_multipliers = [] step_size = 10 lower_border = 10 @@ -467,18 +522,27 @@ def find_secondary_distributions( secondary_amount = 0 # get secondary sum from profile - with open( - create_basic_profile_string(wow_class, wow_spec, settings.tier), 'r' - ) as f: - for line in f: - if "gear_crit_rating=" in line: - secondary_amount += int(line.split("=")[1]) - elif "gear_haste_rating=" in line: - secondary_amount += int(line.split("=")[1]) - elif "gear_mastery_rating=" in line: - secondary_amount += int(line.split("=")[1]) - elif "gear_versatility_rating=" in line: - secondary_amount += int(line.split("=")[1]) + try: + with open( + create_basic_profile_string(wow_class, wow_spec, settings.tier), 'r' + ) as f: + for line in f: + if "gear_crit_rating=" in line: + secondary_amount += int(line.split("=")[1]) + elif "gear_haste_rating=" in line: + secondary_amount += int(line.split("=")[1]) + elif "gear_mastery_rating=" in line: + secondary_amount += int(line.split("=")[1]) + elif "gear_versatility_rating=" in line: + secondary_amount += int(line.split("=")[1]) + except Exception as e: + logger.error( + "Scanning baseline profile {} {} failed. This spec won't be in the result. {}". + format(wow_class, wow_spec, e) + ) + # end this try early, no profile, no calculations + return + logger.debug("secondary_amount found: {}".format(secondary_amount)) # generate all valied secondary distributions @@ -501,6 +565,7 @@ def find_secondary_distributions( "secondary_distributions", wow_class, wow_spec, fight_style ) result_dict["sorted_data_keys"] = {} + result_dict["secondary_sum"] = secondary_amount for talent_combination in talent_combinations: simulation_group = simulation_objects.Simulation_Group( name=talent_combination, @@ -570,15 +635,30 @@ def find_secondary_distributions( ) ) - logger.info("Start secondary distribution simulation.") - simulation_group.simulate() - logger.info("Finished secondary distribution simulation.") + logger.info( + "Start secondary distribution simulation for {} {} {}.".format( + wow_class, wow_spec, talent_combination + ) + ) + + try: + simulation_group.simulate() + except Exception as e: + logger.error( + "Secondary distribution simulation for {} {} {} failed. {}".format( + wow_class, wow_spec, talent_combination, e + ) + ) + continue + else: + logger.info("Finished secondary distribution simulation.") if settings.debug: logger.debug("Talent combination: " + talent_combination) for profile in simulation_group.profiles: logger.debug("{} {}".format(profile.name, profile.get_dps())) + # create sorted list and add data to the result_dict stat_dps_list = [] result_dict["data"][talent_combination] = {} @@ -587,6 +667,7 @@ def find_secondary_distributions( result_dict["data"][talent_combination][profile.name ] = profile.get_dps() + # sort list stat_dps_list = sorted( stat_dps_list, key=lambda item: item[1], reverse=True ) @@ -594,6 +675,8 @@ def find_secondary_distributions( "Sorted secondary distribution list for {}:". format(talent_combination) ) + + # debug print results logger.debug("c h m v dps") for item in stat_dps_list: logger.debug( @@ -605,6 +688,7 @@ def find_secondary_distributions( for item in stat_dps_list: result_dict["sorted_data_keys"][talent_combination].append(item[0]) + # print file logger.debug(result_dict) with open( "results/secondary_distributions/{}_{}_{}.json".format( @@ -614,24 +698,36 @@ def find_secondary_distributions( logger.debug("Print secondary distribution json.") f.write(json.dumps(result_dict, sort_keys=True, indent=4)) logger.debug("Printed secondary distribution json.") + logger.debug("secondary_distribution_simulations ended") def main(): - logger.debug("main started") - # trigger race simulations for elemental shamans - #race_simulations([("shaman", "elemental")]) - # trigger trinket simulations for elemental shamans - # trinket_simulations([ - # #("rogue", "assassination"), - # #("rogue", "outlaw"), - # ("shaman", "elemental") - # ]) - - find_secondary_distributions("shaman", "elemental", ["1111111"]) + logger.debug("main start") + + # TODO: some interface and parameters instead of editing settings.py all the time + # need params especially for + # - executable = "../../SimulationCraft_BfA/simc.exe" + # - threads = "8" + # - profileset_work_threads = "2" + + to_be_simmed_classes_specs = wow_lib.get_classes_specs() + + # trigger race simulations + #race_simulations(to_be_simmed_classes_specs) + # trigger trinket simulations + #trinket_simulations(to_be_simmed_classes_specs) + + # trigger secondary distributions of all dps talent combinations + for wow_class, wow_spec in to_be_simmed_classes_specs: + secondary_distribution_simulations( + wow_class, wow_spec, + wow_lib.get_talent_combinations(wow_class, wow_spec) + ) + logger.debug("main ended") if __name__ == '__main__': - logger.debug("__main__ started") + logger.debug("__main__ start") main() logger.debug("__main__ ended") diff --git a/bloodytools/settings.py b/bloodytools/settings.py index dac370c..dcd3f8e 100644 --- a/bloodytools/settings.py +++ b/bloodytools/settings.py @@ -9,15 +9,15 @@ executable = "../../SimulationCraft_BfA/simc.exe" threads = "8" profileset_work_threads = "2" -target_error = "0.1" -iterations = "25000" -ptr = "1" +target_error = "0.6" +iterations = "250000" +ptr = "0" # General Bloodytools Settings - you usually don't need to touch these debug = True simc_hash = "129d90531685e1e9388127317cabc57b0e5668d2" -# trinkets +# trinket settings min_ilevel = 300 # min_ilevel is used to determine the first simulated itemlevel max_ilevel = 355 # max_itemlevel determines the upper border of steps taken ilevel_step = 10 # ilevel_step is used to determine the size of each itemlevel step taken to max_ilevel diff --git a/bloodytools/simulation_objects/simulation_objects.py b/bloodytools/simulation_objects/simulation_objects.py index 3d700cd..5d1ea44 100644 --- a/bloodytools/simulation_objects/simulation_objects.py +++ b/bloodytools/simulation_objects/simulation_objects.py @@ -703,6 +703,11 @@ def simulate(self) -> bool: else: self.success = True + # remove profilesets file + os.remove(self.filename) + self.filename = None + + # handle broken simulations if fail_counter >= 5: self.logger.error("ERROR: An Error occured during simulation.") self.logger.error("args: " + str(simulation_output.args)) @@ -740,9 +745,6 @@ def simulate(self) -> bool: if "" == line and profileset_results: profileset_results = False - # remove profilesets file - os.remove(self.filename) - else: raise NotSetYetError( "No profiles were added to this simulation_group yet. Nothing can be simulated." diff --git a/requirements.txt b/requirements.txt index d4b5c2e..9da13ab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ # Lib with World of Warcraft game data for simulations and some input checks for SimulationCraft. --e git+https://github.com/Bloodmallet/simc_support.git@v1.0.2#egg=simc_support +-e git+https://github.com/Bloodmallet/simc_support.git@e406dfab83a3619d50bfd42b95489d8b8da8507c#egg=simc_support From d72bb9e51eea92a61f6ad9e1af6126250c90d72c Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Tue, 15 May 2018 01:12:28 +0200 Subject: [PATCH 59/65] fix broken test --- bloodytools/simulation_objects/simulation_objects_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bloodytools/simulation_objects/simulation_objects_tests.py b/bloodytools/simulation_objects/simulation_objects_tests.py index d3b4640..76bd941 100644 --- a/bloodytools/simulation_objects/simulation_objects_tests.py +++ b/bloodytools/simulation_objects/simulation_objects_tests.py @@ -387,7 +387,7 @@ def tearDown(self): def test_empty(self): self.assertEqual( type(simulation_objects.Simulation_Group()), - simulation_objects.simulation_group + simulation_objects.Simulation_Group ) def test_correct_list_input(self): From a4582713cc5c0f700653ff4e1603e625c8ce4b10 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Sat, 19 May 2018 16:43:00 +0200 Subject: [PATCH 60/65] improve settings usage --- bloodytools/bloodytools.py | 64 ++++++++++++++++++++++++++++++-------- bloodytools/settings.py | 50 ++++++++++++++++++++++------- 2 files changed, 89 insertions(+), 25 deletions(-) diff --git a/bloodytools/bloodytools.py b/bloodytools/bloodytools.py index a173276..cafcfa6 100644 --- a/bloodytools/bloodytools.py +++ b/bloodytools/bloodytools.py @@ -1,5 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +"""Welcome to bloodytools - a SimulationCraft automator + + Generate your data more easily without having to create each and every needed profile to do so by hand: + - races + - trinkets + - (NYI) azerite traits + - secondary distributions + + Output is usually saved as .json. But you can add different ways to output the data yourself. + + Contact: https://discord.gg/tFR2uvK Bloodmallet(EU)#8246 + Github: https://github.com/Bloodmallet/bloodytools + Support the developement: + https://www.patreon.com/bloodmallet + https://www.paypal.me/bloodmallet +""" from typing import List, Tuple @@ -414,7 +430,7 @@ def trinket_simulations(specs: List[Tuple[str, str]]) -> None: # LUA data exporter if fight_style.lower() == "patchwerk": - human_readable = True + human_readable = False item_dict = { } # intended structure: itemID -> class -> spec -> itemlevel @@ -510,7 +526,7 @@ def trinket_simulations(specs: List[Tuple[str, str]]) -> None: def secondary_distribution_simulations( wow_class: str, wow_spec: str, - talent_combinations: List[str], + talent_combinations: List[str] = [False], ) -> List[simulation_objects.Simulation_Group]: logger.debug("secondary_distribution_simulations start") @@ -591,7 +607,6 @@ def secondary_distribution_simulations( create_basic_profile_string( wow_class, wow_spec, settings.tier ), - "talents={}".format(talent_combination), "gear_crit_rating={}".format( secondary_amount * (distribution_multiplier[0] / 100) ), @@ -607,6 +622,11 @@ def secondary_distribution_simulations( ] ) ) + # add the talent combination to the profileset, if one was provided + if talent_combination: + simulation_group.profiles[-1].simc_arguments.append( + "talents={}".format(talent_combination) + ) else: simulation_group.add( simulation_objects.Simulation_Data( @@ -710,19 +730,37 @@ def main(): # - threads = "8" # - profileset_work_threads = "2" - to_be_simmed_classes_specs = wow_lib.get_classes_specs() + # empty class-spec list? great, we'll run all classes-specs + if not settings.wow_class_spec_list: + settings.wow_class_spec_list = wow_lib.get_classes_specs() # trigger race simulations - #race_simulations(to_be_simmed_classes_specs) - # trigger trinket simulations - #trinket_simulations(to_be_simmed_classes_specs) + if settings.enable_race_simulations: + race_simulations(settings.wow_class_spec_list) - # trigger secondary distributions of all dps talent combinations - for wow_class, wow_spec in to_be_simmed_classes_specs: - secondary_distribution_simulations( - wow_class, wow_spec, - wow_lib.get_talent_combinations(wow_class, wow_spec) - ) + # trigger trinket simulations + if settings.enable_trinket_simulations: + trinket_simulations(settings.wow_class_spec_list) + + # trigger secondary distributions + if settings.enable_secondary_distributions_simulations: + for wow_class, wow_spec in settings.wow_class_spec_list: + # if multiple talent combintions shall be simed + if settings.talent_permutations: + # if no talent list is provided, simulate all + if not (wow_class, wow_spec) in settings.talent_list: + secondary_distribution_simulations( + wow_class, wow_spec, + wow_lib.get_talent_combinations(wow_class, wow_spec) + ) + else: + # if talent list is provided, sim that + secondary_distribution_simulations( + wow_class, wow_spec, settings.talent_list[(wow_class, wow_spec)] + ) + else: + # sim only the base profile + secondary_distribution_simulations(wow_class, wow_spec) logger.debug("main ended") diff --git a/bloodytools/settings.py b/bloodytools/settings.py index dcd3f8e..e33e7ff 100644 --- a/bloodytools/settings.py +++ b/bloodytools/settings.py @@ -1,24 +1,50 @@ -# Profile setttings -tier = "PR" # number or PR (PreRaid) +"""Settings for bloodytools + + Look for the matching paragraphs of what you want to do. And change settings responsibly. If anything breaks too hard, just reload the settingsfile from the repository. +""" + +## +# General setttings fight_styles = [ "patchwerk", #"beastlord" ] +tier = "PR" # number or PR (PreRaid) +wow_class_spec_list = [] # leave empty to simulate all +# wow_class_spec_list = [("shaman", "elemental"), ("mage", "frost")] # example for a specific list -# Simulation settings +## +# SimulationCraft executable = "../../SimulationCraft_BfA/simc.exe" -threads = "8" -profileset_work_threads = "2" -target_error = "0.6" iterations = "250000" +profileset_work_threads = "2" ptr = "0" - -# General Bloodytools Settings - you usually don't need to touch these -debug = True simc_hash = "129d90531685e1e9388127317cabc57b0e5668d2" +target_error = "0.6" +threads = "8" -# trinket settings -min_ilevel = 300 # min_ilevel is used to determine the first simulated itemlevel -max_ilevel = 355 # max_itemlevel determines the upper border of steps taken +## +# Race simulations +enable_race_simulations = True + +## +# Trinket simulations +enable_trinket_simulations = True ilevel_step = 10 # ilevel_step is used to determine the size of each itemlevel step taken to max_ilevel +max_ilevel = 355 # max_itemlevel determines the upper border of steps taken +min_ilevel = 300 # min_ilevel is used to determine the first simulated itemlevel # example: min 300, max 325, step 10, resulting simulated ilevels: 300, 310, 320 + +## +# Secondary distributions +enable_secondary_distributions_simulations = True +talent_list = { +} # if no list is provided for a class-spec, all dps talent combinations will be run. If you want to only sim the base profiles, set 'talent_permutations' to False +# talent_list = { +# ("shaman", "elemental"): ["1111111", "2222222"], +# } # example for a talent list for Elemental Shamans +talent_permutations = True # set to False, to sim only the base profile talent combinations + +## +# Developement setting - you usually don't need to touch these +debug = True From bb439ff448732a17d97fd59e29da6ba2abddbf8d Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Sat, 19 May 2018 16:43:10 +0200 Subject: [PATCH 61/65] update readme --- README.md | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b708f84..7b663d5 100644 --- a/README.md +++ b/README.md @@ -5,35 +5,48 @@ Bloody( tools ) > Automation tool for several different aspects of all dps and some tank specs in World of Warcraft using SimulationCraft. Ingame customization and number tuning make decision making without external help a bloody hell. ## In developement note -This tool is still in developement and can't be used to generate data yet. If you want to contribute or have suggestions to automate data and representation generation, contact me. +This tool is still in developement but can be used to generate some data. If you want to contribute or have suggestions to automate data and representation generation, contact me. ## Requirements -You need the latest [SimulationCraft](http://downloads.simulationcraft.org/?C=M;O=D) version ([GitHub Repository](https://github.com/simulationcraft/simc)), [Python 3.5](https://www.python.org/downloads/) or newer and the module [simc_support](https://github.com/Bloodmallet/simc_support), which is handled in the requirements.txt. +You need the latest [SimulationCraft](http://downloads.simulationcraft.org/?C=M;O=D) version ([GitHub Repository](https://github.com/simulationcraft/simc)), [Python 3.6](https://www.python.org/downloads/) or newer and the module [simc_support](https://github.com/Bloodmallet/simc_support), which is handled in the requirements.txt. ## Download Download or clone this repository into the SimulationCraft directory. `simulationcraft\bloodytools` -## Setup +## Setup - Short Start python environement. Install dependencies. ```sh $ \Scripts\active ()$ pip install -U -r .\requirements.txt ``` -`pip freeze` should return something like this: "-e git+https://github.com/Bloodmallet/simc_support.git@e806d5ca289072684c6aca5fb03ce2b44e88cc4e#egg=simc_support" +## Setup - First Timers + - [Get Git](https://gitforwindows.org/) + - [Get Python 3.6](https://www.python.org/downloads/), pay attention to install into path (checkbox). + - [Windows] Start the Commandline or PowerShell + - [Linux] Start a Terminal + - `python --version` should return the python 3.6 version number you installed, if it doesn't, try `python3 --version`. Use whatever (python/python3) returned the correct version in the next step + - [Create a virtual 3.6 environment](https://docs.python.org/3/library/venv.html) inside `bloodytools` directory (you might need to start the commandline on windows as administrator to do so), `python3 -m venv env` + - Start the virtual env you just created + - [Windows] `env/Scripts/activate` + - [Linux] `source env/bin/activate` + - "(env)" should appear in front of your line + - Check python version again: `python --version` + - `python -m pip install --upgrade pip` to update the installer of extra tools + - `pip install -r requirements.txt` +Congratulations, you're ready for `Getting started`. ## Getting started -Edit settings.py to your liking using any text editor. Start python environement. Start bloodytools. +Edit settings.py to your liking using a text editor like Notepad++. Start python environement. Start bloodytools. ```sh -$ \Scripts\active -()$ python .\bloodytools.py +()$ python bloodytools.py ``` ## Development If you see a lack of features somewhere or ways to improve the quality of the code, please contact me or create an [issue](https://github.com/Bloodmallet/bloodytools/issues). ## Contact -Meet me in [Discord](https://discord.gg/tFR2uvK). There is a channel #bloodytools. My username is Bloodmallet(EU)#8246. +Meet me in [Discord](https://discord.gg/tFR2uvK). There is a channel #bloodmallet. My username is Bloodmallet(EU)#8246. -## Support -If you want to support the developement: [![PayPal link](https://img.shields.io/badge/PayPal-donate-blue.svg)](https://www.paypal.me/bloodmallet) +## Support the creator +If you want to support the developement: [![PayPal link](https://img.shields.io/badge/PayPal-donate-blue.svg)](https://www.paypal.me/bloodmallet) [![Patreon link](https://img.shields.io/badge/Patreon-pledge-blue.svg)](https://www.patreon.com/bloodmallet) From 32be4f99a83c2ca0f5657cb1245247d4a50ea504 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Tue, 22 May 2018 00:18:45 +0200 Subject: [PATCH 62/65] improve readme further --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b663d5..db855c1 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ $ \Scripts\active - Check python version again: `python --version` - `python -m pip install --upgrade pip` to update the installer of extra tools - `pip install -r requirements.txt` -Congratulations, you're ready for `Getting started`. +Congratulations, you're ready to execute the command of `Getting started` in your already open Commandline/Powershell/Terminal. ## Getting started Edit settings.py to your liking using a text editor like Notepad++. Start python environement. Start bloodytools. From 71d8ec0c80138a7ade1c83ee4599ee322bdaf29a Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Tue, 22 May 2018 00:23:00 +0200 Subject: [PATCH 63/65] added test functionality --- .gitignore | 1 + bloodytools/bloodytools.py | 89 +++- bloodytools/settings.py | 13 +- .../simulation_objects/simulation_objects.py | 424 ++++++++++++++++++ 4 files changed, 513 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index fa801a8..c393eb6 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ /bloodytools/results/secondary_distributions *.simc log.txt +apikey.py diff --git a/bloodytools/bloodytools.py b/bloodytools/bloodytools.py index cafcfa6..2d60741 100644 --- a/bloodytools/bloodytools.py +++ b/bloodytools/bloodytools.py @@ -24,8 +24,12 @@ import logging import settings import simulation_objects.simulation_objects as simulation_objects +import time from simc_support import wow_lib +if settings.use_own_threading: + import threading + # activate logging to file and console logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) @@ -214,7 +218,10 @@ def race_simulations(specs: List[Tuple[str, str]]) -> None: "Start race simulation for {} {}.".format(wow_class, wow_spec) ) try: - simulation_group.simulate() + if settings.use_raidbots and settings.apikey: + simulation_group.simulate_with_raidbots(settings.apikey) + else: + simulation_group.simulate() except Exception as e: logger.error( "Race simulation for {} {} failed. {}".format( @@ -349,7 +356,10 @@ def trinket_simulations(specs: List[Tuple[str, str]]) -> None: "Start trinket simulation for {} {}.".format(wow_class, wow_spec) ) try: - simulation_group.simulate() + if settings.use_raidbots and settings.apikey: + simulation_group.simulate_with_raidbots(settings.apikey) + else: + simulation_group.simulate() except Exception as e: logger.error( "Trinket simulation for {} {} failed. {}".format( @@ -734,13 +744,34 @@ def main(): if not settings.wow_class_spec_list: settings.wow_class_spec_list = wow_lib.get_classes_specs() + # list of all active threads. when empty, terminate tool + thread_list = [] + # trigger race simulations if settings.enable_race_simulations: - race_simulations(settings.wow_class_spec_list) + if settings.use_own_threading: + race_thread = threading.Thread( + name="Race Thread", + target=race_simulations, + args=(settings.wow_class_spec_list, ) + ) + thread_list.append(race_thread) + race_thread.start() + else: + race_simulations(settings.wow_class_spec_list) # trigger trinket simulations if settings.enable_trinket_simulations: - trinket_simulations(settings.wow_class_spec_list) + if settings.use_own_threading: + trinket_thread = threading.Thread( + name="Trinket Thread", + target=trinket_simulations, + args=(settings.wow_class_spec_list, ) + ) + thread_list.append(trinket_thread) + trinket_thread.start() + else: + trinket_simulations(settings.wow_class_spec_list) # trigger secondary distributions if settings.enable_secondary_distributions_simulations: @@ -749,19 +780,55 @@ def main(): if settings.talent_permutations: # if no talent list is provided, simulate all if not (wow_class, wow_spec) in settings.talent_list: - secondary_distribution_simulations( - wow_class, wow_spec, - wow_lib.get_talent_combinations(wow_class, wow_spec) - ) + if settings.use_own_threading: + secondary_distribution_thread = threading.Thread( + name="Secondary Distribution Thread All Talents {} {}".format( + wow_class, wow_spec + ), + target=secondary_distribution_simulations, + args=( + wow_class, wow_spec, + wow_lib.get_talent_combinations(wow_class, wow_spec) + ) + ) + thread_list.append(secondary_distribution_thread) + secondary_distribution_thread.start() + else: + secondary_distribution_simulations( + wow_class, wow_spec, + wow_lib.get_talent_combinations(wow_class, wow_spec) + ) else: # if talent list is provided, sim that - secondary_distribution_simulations( - wow_class, wow_spec, settings.talent_list[(wow_class, wow_spec)] - ) + if settings.use_own_threading: + secondary_distribution_thread = threading.Thread( + name="Secondary Distribution Thread Provided Talents {} {}". + format(wow_class, wow_spec), + target=secondary_distribution_simulations, + args=( + wow_class, wow_spec, + settings.talent_list[(wow_class, wow_spec)] + ) + ) + thread_list.append(secondary_distribution_thread) + secondary_distribution_thread.start() + else: + secondary_distribution_simulations( + wow_class, wow_spec, settings.talent_list[(wow_class, wow_spec)] + ) else: # sim only the base profile secondary_distribution_simulations(wow_class, wow_spec) + while thread_list: + time.sleep(1) + for thread in thread_list: + if thread.is_alive(): + logger.info("{} is still in progress.".format(thread.getName())) + else: + logger.info("{} finished.".format(thread.getName())) + thread_list.remove(thread) + logger.debug("main ended") diff --git a/bloodytools/settings.py b/bloodytools/settings.py index e33e7ff..a74fbc4 100644 --- a/bloodytools/settings.py +++ b/bloodytools/settings.py @@ -12,15 +12,16 @@ tier = "PR" # number or PR (PreRaid) wow_class_spec_list = [] # leave empty to simulate all # wow_class_spec_list = [("shaman", "elemental"), ("mage", "frost")] # example for a specific list +wow_class_spec_list = [("shaman", "elemental")] ## # SimulationCraft executable = "../../SimulationCraft_BfA/simc.exe" -iterations = "250000" +iterations = "50000" profileset_work_threads = "2" ptr = "0" simc_hash = "129d90531685e1e9388127317cabc57b0e5668d2" -target_error = "0.6" +target_error = "0.1" threads = "8" ## @@ -37,7 +38,7 @@ ## # Secondary distributions -enable_secondary_distributions_simulations = True +enable_secondary_distributions_simulations = False talent_list = { } # if no list is provided for a class-spec, all dps talent combinations will be run. If you want to only sim the base profiles, set 'talent_permutations' to False # talent_list = { @@ -48,3 +49,9 @@ ## # Developement setting - you usually don't need to touch these debug = True +use_own_threading = False +use_raidbots = False +try: + from apikey import apikey +except Exception: + pass diff --git a/bloodytools/simulation_objects/simulation_objects.py b/bloodytools/simulation_objects/simulation_objects.py index 5d1ea44..5fa00c1 100644 --- a/bloodytools/simulation_objects/simulation_objects.py +++ b/bloodytools/simulation_objects/simulation_objects.py @@ -757,6 +757,417 @@ def simulate(self) -> bool: return True + def simulate_with_raidbots(self, apikey) -> bool: + """Triggers the simulation of all profiles using Raidbots.com API. + + Raises: + e -- Raised if simulation of a single profile failed. + NotSetYetError -- No data available to simulate. + + Returns: + bool -- True if simulations ended successfully. + """ + if self.profiles: + + self.set_simulation_start_time() + + if len(self.profiles) == 1: + # if only one profiles is in the group this profile is simulated normally + try: + self.profiles[0].simulate() + except Exception as e: + raise e + + elif len(self.profiles) >= 2: + + # write data to file, create file name + if self.filename: + raise AlreadySetError( + "Filename '{}' was already set for the simulation_group. You probably tried to simulate the same group twice.". + format(self.filename) + ) + else: + self.filename = str(uuid.uuid4()) + ".simc" + + # if somehow the random naming function created the same name twice + if os.path.isfile(self.filename): + self.logger.info( + "Somehow the random filename generator generated a name ('{}') that is already on use.". + format(self.filename) + ) + self.filename = str(uuid.uuid4()) + ".simc" + # write arguments to file + with open(self.filename, "w") as f: + # write the equal values to file + f.write( + "default_actions={}\n".format(self.profiles[0].default_actions) + ) + f.write( + "default_skill={}\n".format(self.profiles[0].default_skill) + ) + f.write("fight_style={}\n".format(self.profiles[0].fight_style)) + f.write("fixed_time={}\n".format(self.profiles[0].fixed_time)) + f.write("iterations={}\n".format(self.profiles[0].iterations)) + f.write( + "optimize_expressions={}\n".format( + self.profiles[0].optimize_expressions + ) + ) + f.write("ptr={}\n".format(self.profiles[0].ptr)) + f.write("target_error={}\n".format(self.profiles[0].target_error)) + + # write all specific arguments to file + for profile in self.profiles: + # first used profile needs to be written as normal profile instead of profileset + if profile == self.profiles[0]: + for argument in profile.simc_arguments: + # first profile has the dynamic link to the base profile as the first argument + if argument == profile.simc_arguments[0]: + with open(argument, 'r') as basic_profile: + for line in basic_profile: + f.write(line) # TODO: might need a line break + else: + f.write("{}\n".format(argument)) + f.write( + "name=\"{}\"\n\n# Profileset start\n".format(profile.name) + ) + # or else in wrong scope + f.write( + "ready_trigger={}\n".format(self.profiles[0].ready_trigger) + ) + + else: + for special_argument in profile.simc_arguments: + f.write( + "profileset.\"{profile_name}\"+={argument}\n".format( + profile_name=profile.name, argument=special_argument + ) + ) + + # need raidbots logic + + # create advanced input string + raidbots_advancedInput = "" + with open(self.filename, 'r') as f: + for line in f: + raidbots_advancedInput += line + + raidbots_body = { + "type": "advanced", + "apiKey": apikey, + "advancedInput": raidbots_advancedInput, + "simcVersion": "bfa-dev", + "reportName": "Bloodmallet's shitty tool", + } + + self.logger.debug( + "Raidbots communication body created: {}".format(raidbots_body) + ) + + from urllib import request, error + import json + + data = json.dumps(raidbots_body).encode('utf8') + headers = { + 'Content-Type': 'application/json', + 'User-Agent': 'Bloodmallet\'s shitty tool' + } + req = request.Request( + "https://www.raidbots.com/sim", data=data, headers=headers + ) + + try: + response = request.urlopen(req) + except error.URLError as e: + self.logger.error( + "Sending the task {} to Raidbots failed.".format(self.name) + ) + self.logger.error(e.reason) + self.logger.error(e.read()) + raise SimulationError( + "Communication with Raidbots failed. {}".format(e.reason) + ) + else: + try: + raidbots_response = json.loads(response.read()) + except Exception as e: + self.logger.error(e) + raise e + else: + self.logger.debug( + "Raidbots responded: {}".format(raidbots_response) + ) + + raidbots_sim_id = raidbots_response["simId"] + + # check for when the simulation is done + self.logger.info( + "Simulation of {} is underway. Please wait".format(self.name) + ) + + try: + progress = json.loads( + request.urlopen( + "https://www.raidbots.com/api/job/{}".format(raidbots_sim_id) + ).read() + ) + except: + self.logger.error( + "Fetching the progress of the simulation of {} failed.".format( + self.name + ) + ) + progress = {"job": {"state": ""}} + else: + self.logger.info( + "Simulation {} is at {}. This value might behave unpredictable.". + format(self.name, progress["job"]["progress"]) + ) + + import time + while not progress["job"]["state"] == "complete" and not progress[ + "job" + ]["state"] == "failed": + time.sleep(6.5) + try: + progress = json.loads( + request.urlopen( + request.Request( + "https://www.raidbots.com/api/job/{}". + format(raidbots_sim_id), + headers=headers + ) + ).read() + ) + except Exception as e: + self.logger.error(e) + else: + progress = json.loads( + request.urlopen( + request.Request( + "https://www.raidbots.com/api/job/{}". + format(raidbots_sim_id), + headers=headers + ) + ).read() + ) + self.logger.info( + "Simulation {} is at {}. This value might behave unpredictable.". + format(self.name, progress["job"]["progress"]) + ) + self.logger.debug(progress) + self.logger.info( + "Simulation {} is done. Fetching data. This might take some seconds.". + format(self.name) + ) + + # simulation is done, get data + fetch_data_start_time = datetime.datetime.utcnow( + ) + datetime.timedelta(0, 30) + fetch_data_timeout = False + try: + raidbots_data = json.loads( + request.urlopen( + request.Request( + "https://www.raidbots.com/reports/{}/data.json". + format(raidbots_sim_id), + headers=headers + ) + ).read() + ) + except Exception as e: + self.logger.error( + "Fetching data for {} from raidbots failed. {}".format( + self.name, e + ) + ) + fetched = False + while not fetched and not fetch_data_timeout: + time.sleep(4) + try: + raidbots_data = json.loads( + request.urlopen( + request.Request( + "https://www.raidbots.com/reports/{}/data.json". + format(raidbots_sim_id), + headers=headers + ) + ).read() + ) + except Exception as e: + self.logger.error( + "Fetching data for {} from raidbots failed. {}".format( + self.name, e + ) + ) + if datetime.datetime.utcnow() > fetch_data_start_time: + fetch_data_timeout = True + else: + fetched = True + else: + self.logger.info( + "Fetching data for {} succeeded. {}".format( + self.name, raidbots_data + ) + ) + + # if simulation failed or data.json couldn't be fetched + if progress["job"]["state"] == "failed" or fetch_data_timeout: + + fetch_input_start_time = datetime.datetime.utcnow( + ) + datetime.timedelta(0, 30) + fetch_input_timeout = False + + # simulation failed, get input + try: + raidbots_input = json.loads( + request.urlopen( + request.Request( + "https://www.raidbots.com/reports/{}/input.txt". + format(raidbots_sim_id), + headers=headers + ) + ).read() + ) + except Exception as e: + self.logger.error( + "Fetching input data for {} from raidbots failed. {}".format( + self.name, e + ) + ) + fetched = False + while not fetched and not fetch_input_timeout: + try: + raidbots_input = json.loads( + request.urlopen( + request.Request( + "https://www.raidbots.com/reports/{}/input.txt". + format(raidbots_sim_id), + headers=headers + ) + ).read() + ) + except Exception as e: + self.logger.error( + "Fetching input data for {} from raidbots failed. {}". + format(self.name, e) + ) + if datetime.datetime.utcnow() > fetch_input_start_time: + fetch_input_timeout = True + else: + fetched = True + else: + self.logger.info( + "Fetching input data for {} succeeded. {}".format( + self.name, raidbots_input + ) + ) + + fetch_output_start_time = datetime.datetime.utcnow( + ) + datetime.timedelta(0, 30) + fetch_output_timeout = False + + # simulation failed, get input + try: + raidbots_output = json.loads( + request.urlopen( + request.Request( + "https://www.raidbots.com/reports/{}/output.txt". + format(raidbots_sim_id), + headers=headers + ) + ).read() + ) + except Exception as e: + self.logger.error( + "Fetching output data for {} from raidbots failed. {}".format( + self.name, e + ) + ) + fetched = False + while not fetched and not fetch_output_timeout: + try: + raidbots_output = json.loads( + request.urlopen( + request.Request( + "https://www.raidbots.com/reports/{}/output.txt". + format(raidbots_sim_id), + headers=headers + ) + ).read() + ) + except Exception as e: + self.logger.error( + "Fetching output data for {} from raidbots failed. {}". + format(self.name, e) + ) + if datetime.datetime.utcnow() > fetch_output_start_time: + fetch_output_timeout = True + else: + fetched = True + else: + self.logger.info( + "Fetching output data for {} succeeded. {}".format( + self.name, raidbots_output + ) + ) + + with open("error_{}.txt".format(raidbots_sim_id), 'w') as f: + f.write("############## INPUT ##############\n") + f.write(json.dumps(raidbots_input)) + f.write("\n\n############## OUTPUT ##############\n") + f.write(json.dumps(raidbots_output)) + + try: + f.write( + "\n\n############## DATA ##############\n{}".format( + json.dumps(raidbots_data) + ) + ) + except Exception: + f.write( + "\n\n############## DATA ##############\nNo data available.\n" + ) + + raise SimulationError( + "Simulating with Raidbots failed. Please check out error_{}.txt file.". + format(raidbots_sim_id) + ) + + input("Enter something to delete the created file.") + # remove profilesets file + os.remove(self.filename) + self.filename = None + + self.logger.debug("Congratulations, you got a data file!") + self.logger.debug( + json.dumps(raidbots_data, sort_keys=True, indent=4) + ) + + # set basic profile dps + self.logger.debug("Setting dps for baseprofile.") + self.set_dps_of( + raidbots_data["sim"]["players"][0]["name"], + raidbots_data["sim"]["players"][0]["collected_data"]["dps"]["mean"] + ) + self.logger.debug("Set dps for baseprofile.") + + for profile in raidbots_data["sim"]["profilesets"]["results"]: + self.logger.debug("Setting dps for {}".format(profile["name"])) + self.set_dps_of(profile["name"], profile["mean"]) + + else: + raise NotSetYetError( + "No profiles were added to this simulation_group yet. Nothing can be simulated." + ) + + self.set_simulation_end_time() + + else: + return False + + return True + def add(self, simulation_instance: Simulation_Data) -> bool: """Add another simulation_instance object to the group. @@ -805,3 +1216,16 @@ def get_dps_of(self, profile_name: str) -> int: if profile.name == profile_name: return profile.get_dps() return None + + def set_dps_of(self, profile_name: str, dps: Union[int, float, str]) -> bool: + try: + for profile in self.profiles: + if profile.name == profile_name: + profile.set_dps(dps, external=False) + except Exception as e: + self.logger.error( + "Setting dps for profile {} failed. {}".format(profile_name, e) + ) + return False + else: + return True From 2a0b42c60fd251c67fb2b4f0d5be3f1d7f8f4ed4 Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Tue, 22 May 2018 13:34:26 +0200 Subject: [PATCH 64/65] add arguments, call with `-h` to see a list --- bloodytools/bloodytools.py | 111 +++++++++++++++++++++++++++++++++++-- bloodytools/settings.py | 10 ++-- 2 files changed, 110 insertions(+), 11 deletions(-) diff --git a/bloodytools/bloodytools.py b/bloodytools/bloodytools.py index 2d60741..7fddd2c 100644 --- a/bloodytools/bloodytools.py +++ b/bloodytools/bloodytools.py @@ -19,6 +19,7 @@ from typing import List, Tuple +import argparse import datetime import json import logging @@ -32,7 +33,10 @@ # activate logging to file and console logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) +logger.setLevel(logging.INFO) +if hasattr(settings, "debug"): + if settings.debug: + logger.setLevel(logging.DEBUG) # file logger fh = logging.FileHandler("log.txt", "w") fh.setLevel(logging.ERROR) @@ -294,7 +298,7 @@ def trinket_simulations(specs: List[Tuple[str, str]]) -> None: pass except Exception as e: logger.error( - "Opening baseline profile {} {} failed. This spec won't be in the result. {}". + "Opening baseline profile for {} {} failed. This spec won't be in the result. {}". format(wow_class, wow_spec, e) ) # end this try early, no profile, no calculations @@ -371,7 +375,7 @@ def trinket_simulations(specs: List[Tuple[str, str]]) -> None: logger.debug("Finished trinket simulation.") for profile in simulation_group.profiles: - logger.info( + logger.debug( "{} {} DPS, baseline +{}".format( profile.name, profile.get_dps(), profile.get_dps() - simulation_group.get_dps_of( @@ -740,8 +744,103 @@ def main(): # - threads = "8" # - profileset_work_threads = "2" - # empty class-spec list? great, we'll run all classes-specs - if not settings.wow_class_spec_list: + parser = argparse.ArgumentParser( + description="Simulate different aspects of World of Warcraft data." + ) + parser.add_argument( + "-a", + "--all", + dest="sim_all", + action="store_const", + const=True, + default=False, + help= + "Simulate races, trinkets, secondary distributions, and azerite traits (NYI) for all specs and all talent combinations." + ) + parser.add_argument( + "--executable", + metavar="PATH", + type=str, + help="Relative path to SimulationCrafts executable. Default: '{}'".format( + settings.executable + ) + ) + parser.add_argument( + "--profileset_work_threads", + metavar="NUMBER", + type=str, + help= + "Number of threads used per profileset by SimulationCraft. Default: '{}'". + format(settings.profileset_work_threads) + ) + parser.add_argument( + "--threads", + metavar="NUMBER", + type=str, + help="Number of threads used by SimulationCraft. Default: '{}'".format( + settings.threads + ) + ) + parser.add_argument( + "--target_error", + metavar="NUMBER", + type=str, + help="Accuracy used by SimulationCraft. Default: '{}'".format( + settings.target_error + ) + ) + parser.add_argument( + "--debug", + action="store_const", + const=True, + default=False, + help="Enables debug modus. Default: '{}'".format(settings.debug) + ) + args = parser.parse_args() + + # activate debug mode as early as possible + if args.debug: + settings.debug = args.debug + logger.setLevel(logging.DEBUG) + fh.setLevel(logging.DEBUG) + logger.debug("Set debug mode to {}".format(settings.debug)) + + # if argument specified to simulate all, override settings + if args.sim_all: + settings.enable_race_simulations = True + settings.enable_secondary_distributions_simulations = True + settings.enable_trinket_simulations = True + # set talent_list to empty to ensure all talent combinations are run + settings.talent_list = {} + logger.debug( + "Set enable_race_simulations, enable_secondary_distributions_simulations, and enable_trinket_simulations to True." + ) + + # set new executable path if provided + if args.executable: + settings.executable = args.executable + logger.debug("Set executable to {}".format(settings.executable)) + + # set new threads if provided + if args.threads: + settings.threads = args.threads + logger.debug("Set threads to {}".format(settings.threads)) + + # set new profileset_work_threads if provided + if args.profileset_work_threads: + settings.profileset_work_threads = args.profileset_work_threads + logger.debug( + "Set profileset_work_threads to {}".format( + settings.profileset_work_threads + ) + ) + + if args.target_error: + settings.target_error = args.target_error + logger.debug("Set target_error to {}".format(settings.target_error)) + + # empty class-spec list or argument was provided to run all? great, we'll run all classes-specs + if not settings.wow_class_spec_list or args.sim_all: settings.wow_class_spec_list = wow_lib.get_classes_specs() # list of all active threads. when empty, terminate tool @@ -824,7 +923,7 @@ def main(): time.sleep(1) for thread in thread_list: if thread.is_alive(): - logger.info("{} is still in progress.".format(thread.getName())) + logger.debug("{} is still in progress.".format(thread.getName())) else: logger.info("{} finished.".format(thread.getName())) thread_list.remove(thread) diff --git a/bloodytools/settings.py b/bloodytools/settings.py index a74fbc4..1f3ace7 100644 --- a/bloodytools/settings.py +++ b/bloodytools/settings.py @@ -17,16 +17,16 @@ ## # SimulationCraft executable = "../../SimulationCraft_BfA/simc.exe" -iterations = "50000" +iterations = "75000" profileset_work_threads = "2" ptr = "0" -simc_hash = "129d90531685e1e9388127317cabc57b0e5668d2" -target_error = "0.1" +simc_hash = "dec9b12600b28a81a11eef118b677116b9af4955" +target_error = "0.4" threads = "8" ## # Race simulations -enable_race_simulations = True +enable_race_simulations = False ## # Trinket simulations @@ -48,7 +48,7 @@ ## # Developement setting - you usually don't need to touch these -debug = True +debug = False use_own_threading = False use_raidbots = False try: From d73485eb82bc57d46027437d62b09762e68f22cd Mon Sep 17 00:00:00 2001 From: Bloodmallet Date: Wed, 23 May 2018 17:46:48 +0200 Subject: [PATCH 65/65] improve user messages slightly --- bloodytools/bloodytools.py | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/bloodytools/bloodytools.py b/bloodytools/bloodytools.py index 7fddd2c..207b144 100644 --- a/bloodytools/bloodytools.py +++ b/bloodytools/bloodytools.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -"""Welcome to bloodytools - a SimulationCraft automator +"""Welcome to bloodytools - a SimulationCraft automator/wrapper Generate your data more easily without having to create each and every needed profile to do so by hand: - races @@ -15,6 +15,8 @@ Support the developement: https://www.patreon.com/bloodmallet https://www.paypal.me/bloodmallet + + May 2018 """ from typing import List, Tuple @@ -737,13 +739,9 @@ def secondary_distribution_simulations( def main(): logger.debug("main start") + logger.info("Bloodytools at your service.") - # TODO: some interface and parameters instead of editing settings.py all the time - # need params especially for - # - executable = "../../SimulationCraft_BfA/simc.exe" - # - threads = "8" - # - profileset_work_threads = "2" - + # interface parameters parser = argparse.ArgumentParser( description="Simulate different aspects of World of Warcraft data." ) @@ -839,6 +837,8 @@ def main(): settings.target_error = args.target_error logger.debug("Set target_error to {}".format(settings.target_error)) + bloodytools_start_time = datetime.datetime.utcnow() + # empty class-spec list or argument was provided to run all? great, we'll run all classes-specs if not settings.wow_class_spec_list or args.sim_all: settings.wow_class_spec_list = wow_lib.get_classes_specs() @@ -848,6 +848,9 @@ def main(): # trigger race simulations if settings.enable_race_simulations: + if not settings.use_own_threading: + logger.info("Starting Race simulations.") + if settings.use_own_threading: race_thread = threading.Thread( name="Race Thread", @@ -859,8 +862,14 @@ def main(): else: race_simulations(settings.wow_class_spec_list) + if not settings.use_own_threading: + logger.info("Race simulations finished.") + # trigger trinket simulations if settings.enable_trinket_simulations: + if not settings.use_own_threading: + logger.info("Starting Trinket simulations.") + if settings.use_own_threading: trinket_thread = threading.Thread( name="Trinket Thread", @@ -872,8 +881,14 @@ def main(): else: trinket_simulations(settings.wow_class_spec_list) + if not settings.use_own_threading: + logger.info("Trinket simulations finished.") + # trigger secondary distributions if settings.enable_secondary_distributions_simulations: + if not settings.use_own_threading: + logger.info("Starting Secondary Distribution simulations.") + for wow_class, wow_spec in settings.wow_class_spec_list: # if multiple talent combintions shall be simed if settings.talent_permutations: @@ -919,6 +934,9 @@ def main(): # sim only the base profile secondary_distribution_simulations(wow_class, wow_spec) + if not settings.use_own_threading: + logger.info("Secondary Distribution simulations finished.") + while thread_list: time.sleep(1) for thread in thread_list: @@ -928,6 +946,10 @@ def main(): logger.info("{} finished.".format(thread.getName())) thread_list.remove(thread) + logger.info( + "Bloodytools took {} to finish.". + format(datetime.datetime.utcnow() - bloodytools_start_time) + ) logger.debug("main ended")