Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
429 changes: 41 additions & 388 deletions internal/analyze.py

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions internal/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@
import json
import time
import requests
import yaml
import tqdm

with open("config.yml", "r") as ymlfile:
config = yaml.load(ymlfile, Loader=yaml.FullLoader)
from internal.config import config

num_of_retries = int(config["raidbots"]["numOfRetries"])
retry_interval = int(config["raidbots"]["retryInterval"])
Expand Down
3 changes: 0 additions & 3 deletions internal/auto_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ def download_latest():

# Unpack downloaded build and set simc_path
dir_name = filepath[: filepath.find(".7z")]
print(download_dir, dir_name)
simc_path = os.path.join(download_dir, dir_name, "simc.exe")
if not os.path.exists(simc_path):
_unpack_file(seven_zip_executable, filepath, download_dir)
Expand All @@ -58,8 +57,6 @@ def _find_7zip(search_paths):
for exe in search_paths:
try:
if not os.path.exists(exe):
print(
f"7Zip executable at '{exe}' does not exist, or is not executable.")
continue
return exe
except OSError:
Expand Down
6 changes: 6 additions & 0 deletions internal/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'internal configuration - exposes config.yml as a dict'
import yaml

config = {}
with open("config.yml", "r") as ymlfile:
config = yaml.load(ymlfile, Loader=yaml.FullLoader)
30 changes: 17 additions & 13 deletions internal/sim_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import json
from os import path

from internal import utils


def parse(filename, weights):
"""parse the given sim file"""
Expand All @@ -19,7 +21,7 @@ def parse(filename, weights):
results = sim['sim']['players']
for player in sorted(results, key=lambda k: k['name']):
if not weights or 'Int' in player['scale_factors']:
ret += path.splitext(filename)[0] + separator
ret += path.splitext(os.path.basename(filename))[0] + separator
ret += player['name'] + separator
ret += '{0:.{1}f}'.format(player['collected_data']
['dmg']['mean'], 0) + separator
Expand Down Expand Up @@ -47,7 +49,7 @@ def parse_profile_sets(filename, weights):
sim = json.loads(data)
results = sim['sim']['profilesets']['results']
for profile in sorted(results, key=lambda k: k['name']):
ret += path.splitext(filename)[0] + separator
ret += path.splitext(os.path.basename(filename))[0] + separator
ret += profile['name'] + separator
ret += '{0:.{1}f}'.format(0, 0) + separator
ret += '{0:.{1}f}'.format(profile['mean'], 0) + separator
Expand All @@ -57,23 +59,25 @@ def parse_profile_sets(filename, weights):

def parse_json(directory, weights):
"""parse json files"""
os.chdir(directory)
parses = 'profile,actor,DD,DPS'
headers = ['profile','actor','DD','DPS']
if weights:
parses += ',int,haste,crit,mastery,vers'
parses += '\n'
for filename in os.listdir(os.getcwd()):
headers += ['int','haste','crit','mastery','vers']
parses = ','.join(headers) + '\n'

for filename in os.listdir(directory):
if filename.endswith('.json'):
parses += parse(filename, weights)
with open("statweights.csv", "w") as ofile:
print(parses, file=ofile)
parses += parse(os.path.join(directory, filename), weights)

with open(os.path.join(directory, "statweights.csv"), "w") as ofile:
ofile.write(parses)


def get_timestamp():
def get_timestamp(directory, talent, covenant):
"""get timestamp the sim was run"""
for filename in os.listdir(os.getcwd()):
full_path = os.path.join(directory, utils.get_simc_dir(talent, covenant, 'output'))
for filename in os.listdir(full_path):
if filename.endswith('.json'):
with open(filename, "r") as file:
with open(os.path.join(full_path, filename), "r") as file:
data = file.read()
sim = json.loads(data)
timestamp = sim['timestamp']
Expand Down
75 changes: 75 additions & 0 deletions internal/tests/test_analyze.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
'tests for analyze module'

import os
import sys
from unittest.mock import call
sys.path.insert(0, os.path.abspath( # This should be the path back to the root directory.
os.path.join(os.path.dirname(__file__), '..', '..')))

from internal.analyze import analyze # pylint: disable=wrong-import-position


def test_analyze(mocker):
'test for the main analyze function'
spy_pandas = mocker.patch('pandas.read_csv', return_value={})
spy_build_results = mocker.patch(
'internal.analyze.build_results', return_value={})
spy_build_md = mocker.patch(
'internal.analyze.build_markdown', return_value=None)
spy_build_csv = mocker.patch(
'internal.analyze.build_csv', return_value=None)
spy_build_json = mocker.patch(
'internal.analyze.build_json', return_value=None)

# Not dungeon run
analyze("talent", "gear", False, "weights", "timestamp", "covenant")

spy_pandas.assert_called_once_with(
os.path.join('gear', 'output', 'talent', 'covenant', 'statweights.csv'),
usecols=['profile', 'actor', 'DD', 'DPS',
'int', 'haste', 'crit', 'mastery', 'vers']
)
spy_build_results.assert_has_calls([
call({}, 'weights', 'Composite', 'gear'),
call({}, 'weights', 'Single', 'gear')
])
spy_build_md.assert_has_calls([
call('Composite', '_talent', {}, 'gear', 'weights', None, '_covenant'),
call('Single', '_talent', {}, 'gear', 'weights', None, '_covenant')
])
spy_build_csv.assert_has_calls([
call('Composite', '_talent', {}, 'gear', 'weights', None, '_covenant'),
call('Single', '_talent', {}, 'gear', 'weights', None, '_covenant')
])
spy_build_json.assert_not_called()


def test_analyze_dungeon_run(mocker):
'tests running analyze with the dungeon flag set true'
spy_pandas = mocker.patch('pandas.read_csv', return_value={})
spy_build_results = mocker.patch(
'internal.analyze.build_results', return_value={})
spy_build_md = mocker.patch(
'internal.analyze.build_markdown', return_value=None)
spy_build_csv = mocker.patch(
'internal.analyze.build_csv', return_value=None)
spy_build_json = mocker.patch(
'internal.analyze.build_json', return_value=None)

# Dungeon run
analyze("talent", "gear", True, "weights", "timestamp", "covenant")

spy_pandas.assert_called_once_with(
os.path.join('gear', 'output', 'talent', 'covenant', 'statweights.csv'),
usecols=['profile', 'actor', 'DD', 'DPS',
'int', 'haste', 'crit', 'mastery', 'vers']
)
spy_build_results.assert_called_once_with(
{}, 'weights', 'Dungeons', 'gear')
spy_build_md.assert_called_once_with(
'Dungeons', '_talent', {}, 'gear', 'weights', None, '_covenant'
)
spy_build_csv.assert_called_once_with(
'Dungeons', '_talent', {}, 'gear', 'weights', None, '_covenant'
)
spy_build_json.assert_not_called()
10 changes: 4 additions & 6 deletions internal/tests/test_auto_download.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
'auto_download test file'

from unittest.mock import Mock
import os
import sys
sys.path.insert(0, os.path.abspath(
sys.path.insert(0, os.path.abspath( # This should be the path back to the root directory.
os.path.join(os.path.dirname(__file__), '..', '..')))

from internal.auto_download import download_latest, BASE_URL, _cleanup_older_files, _find_7zip # pylint: disable=wrong-import-position
from internal.auto_download import download_latest, BASE_URL, _cleanup_older_files, _find_7zip # pylint: disable=wrong-import-position


def assert_not_called_with(self, *args, **kwargs):
Expand All @@ -16,7 +14,7 @@ def assert_not_called_with(self, *args, **kwargs):
except AssertionError:
return
raise AssertionError('Expected %s to not have been called.' %
self._format_mock_call_signature(args, kwargs)) # pylint: disable=protected-access
self._format_mock_call_signature(args, kwargs)) # pylint: disable=protected-access


Mock.assert_not_called_with = assert_not_called_with
Expand Down Expand Up @@ -92,5 +90,5 @@ def test_find_seven_zip(mocker):
path = _find_7zip(paths)

assert path == "path-2"
for path in paths[:-1]: # Removing the last entry
for path in paths[:-1]: # Removing the last entry
spy.assert_any_call(path)
20 changes: 20 additions & 0 deletions internal/tests/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'tests for config module'

import os
import sys
import yaml

sys.path.insert(0, os.path.abspath( # This should be the path back to the root directory.
os.path.join(os.path.dirname(__file__), '..', '..')))

from internal.config import config # pylint: disable=wrong-import-position

def test_config():
'test loading the config'
original_config = None
with open("config.yml", "r") as ymlfile:
original_config = yaml.load(ymlfile, Loader=yaml.FullLoader)
assert original_config is not None, 'unable to load config'

# yeah this test is pretty eh, but not sure what to do here.
assert config == original_config
19 changes: 9 additions & 10 deletions internal/utils.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
"""stores utils that are shared between scripts"""
import os
import argparse
import yaml

with open("config.yml", "r") as ymlfile:
config = yaml.load(ymlfile, Loader=yaml.FullLoader)
from internal.config import config


def get_talents(args):
"""lookup talents based on current config"""
if args.talents:
talents = [args.talents]
elif config["sims"][args.dir[:-1]]["builds"]:
elif config["sims"][args.dir]["builds"]:
Comment on lines -13 to +12

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wont this fail? Since args.dir is talents/ but we need to lookup based on talents

@psykzz psykzz Nov 22, 2020

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No since we now normalise the path, so a user could do ./talents or ./talents/ it would be normalised to talents

See https://github.com/WarcraftPriests/sl-shadow-priest/pull/194/files#diff-ec7f77098f7800c209790b5e76053a876d0bb993248972a80a8941a573ba9323R213

talents = config["builds"].keys()
else:
talents = []
Expand All @@ -21,7 +20,7 @@ def get_covenant(args):
"""lookup covenant based on current config"""
if args.covenant:
covenants = [args.covenant]
elif config["sims"][args.dir[:-1]]["covenant"]["lookup"]:
elif config["sims"][args.dir]["covenant"]["lookup"]:
covenants = config["covenants"]["list"]
else:
covenants = []
Expand All @@ -32,15 +31,15 @@ def get_simc_dir(talent, covenant, folder_name):
"""get proper directory based on talent and covenant options"""
if covenant:
if talent:
return "{0}/{1}/{2}/".format(folder_name, talent, covenant)
return "{0}/{1}/".format(folder_name, covenant)
return os.path.join(folder_name, talent, covenant)
return os.path.join(folder_name, covenant)
if talent:
return "{0}/{1}/".format(folder_name, talent)
return "{0}/".format(folder_name)
return os.path.join(folder_name, talent)
return folder_name


def generate_parser(description):
"""creates the shared argparser for sim.pu and profiles.py"""
"""creates the shared argparser for sim.py and profiles.py"""
parser = argparse.ArgumentParser(description=description)
parser.add_argument('dir', help='Directory to generate profiles for.')
parser.add_argument(
Expand Down
104 changes: 104 additions & 0 deletions internal/writers/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
'Common helpers for writers'
import os

from internal.config import config
from internal.weights import find_weights
from internal.spell_ids import find_ids


def generate_report_name(sim_type, talent=None, covenant=None):
"""create report name based on talents and covenant"""
talents = f" - {talent.strip('_')}" if talent else ""
covenant = f" - {covenant.strip('_')}" if covenant else ""
return f"{sim_type}{talents}{covenant}"


def assure_path_exists(path):
"""Make sure the path exists and contains a folder"""
dir_name = os.path.dirname(path)
if not os.path.exists(dir_name):
os.makedirs(dir_name)


def build_output_string(base_path, sim_type, talent, covenant, file_ext):
"""creates output string for the results file"""
output_dir = os.path.join(base_path, "results")
assure_path_exists(output_dir)
return os.path.join(output_dir, f"Results_{sim_type}{talent}{covenant}.{file_ext}")


def lookup_id(name, directory):
"""lookup the spell or item id of an item name"""
lookup_type = config["sims"][directory]["lookupType"]
if lookup_type == "spell":
return lookup_spell_id(name, directory)
if lookup_type == "item":
return lookup_item_id(name, directory)
if lookup_type == "none":
return None
print(f"Could not find id for {name}")
return None


def lookup_spell_id(spell_name, directory):
"""lookup a spell name from the ids dict"""
ids = find_ids(directory)
if ids:
return ids.get(spell_name)
print(f"Could not find spell id for {spell_name}")
return None


def lookup_item_id(item_name, directory):
"""
get the list of sim files from config
loop over them and search for the item name line by line
"""
for sim_file in config["sims"][directory]["files"]:
with open(sim_file, 'r') as file:
for line in file:
if item_name in line:
# find ,id= -> take 2nd half ->
# find , -> take 1st half
return int(line.split(',id=')[1].split(',')[0])
return None


def convert_increase_to_double(increase):
"""convert string increase to double"""
increase = increase.strip('%')
increase = round(float(increase), 4)
if increase:
increase = round(increase / 100, 4)
return increase


def get_change(current, previous):
"""gets the percent change between two numbers"""
negative = 0
if current < previous:
negative = True
try:
value = (abs(current - previous) / previous) * 100.0
value = float('%.2f' % value)
if value >= 0.01 and negative:
value = value * -1
return value
except ZeroDivisionError:
return 0


def find_weight(sim_type, profile_name):
"""looks up the weight based on the sim type"""
weight_type = ""
if sim_type == "Composite":
weight_type = "compositeWeights"
elif sim_type == "Single":
weight_type = "singleTargetWeights"
elif sim_type == "Dungeons":
# Dungeon sim is just 1 sim, so we return 1 here
return 1
weight = find_weights(config[weight_type]).get(profile_name)
if not weight:
return 0
return weight
Loading