From f8a011094dacfa2dfefda053edeebc47596cc3ab Mon Sep 17 00:00:00 2001 From: chriddyp Date: Fri, 15 Sep 2017 15:10:45 -0400 Subject: [PATCH 01/21] bump to latest dash --- dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 0055fd7..df68515 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,6 +1,6 @@ dash_core_components==0.12.0 dash_html_components==0.7.0 -dash==0.18.0 +dash==0.18.3 percy selenium mock From d87877f088a912bdc0227cf63c18bb73c895b3d4 Mon Sep 17 00:00:00 2001 From: chriddyp Date: Fri, 15 Sep 2017 15:10:56 -0400 Subject: [PATCH 02/21] demonstrate failing case in a test --- tests/test_render.py | 51 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/test_render.py b/tests/test_render.py index 9ca4265..56eea31 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -9,6 +9,7 @@ import time import re import itertools +import json class Tests(IntegrationTests): @@ -1415,3 +1416,53 @@ def chapter2_assertions(): time.sleep(2) # liberally wait for the front-end to process request chapter2_assertions() assert_clean_console(self) + + def test_rendering_new_content_calls_callback_once_per_output(self): + app = Dash(__name__) + call_count = Value('i', 0) + + app.config['suppress_callback_exceptions'] = True + app.layout = html.Div([ + html.Button( + id='display-content', + children='Display Content', + n_clicks=0 + ), + html.Div(id='container'), + dcc.RadioItems() + ]) + + @app.callback( + Output('container', 'children'), + [Input('display-content', 'n_clicks')]) + def display_output(n_clicks): + print('display_output ' + str(n_clicks)) + if n_clicks == 0: + return '' + return html.Div([ + html.Div([ + dcc.Input( + value='Input {}'.format(i), + id='input-{}'.format(i) + ) + for i in range(10) + ]), + html.Div(id='dynamic-output') + ]) + + @app.callback( + Output('dynamic-output', 'children'), + [Input('input-{}'.format(i), 'value') for i in range(10)]) + def dynamic_output(*args): + call_count.value += 1 + return json.dumps(args, indent=2) + + self.startServer(app) + + self.wait_for_element_by_id('display-content').click() + + time.sleep(5) + + self.percy_runner.snapshot(name='layout') + + self.assertEqual(call_count.value, 1) From eacfe1f689f4253a94c508e72445f2815e592938 Mon Sep 17 00:00:00 2001 From: chriddyp Date: Fri, 15 Sep 2017 15:25:38 -0400 Subject: [PATCH 03/21] add failing test for loading initial layout as well --- tests/test_render.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/test_render.py b/tests/test_render.py index 56eea31..9cf22cd 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -1417,6 +1417,40 @@ def chapter2_assertions(): chapter2_assertions() assert_clean_console(self) + def test_rendering_layout_calls_callback_once_per_output(self): + app = Dash(__name__) + call_count = Value('i', 0) + + app.config['suppress_callback_exceptions'] = True + app.layout = html.Div([ + html.Div([ + dcc.Input( + value='Input {}'.format(i), + id='input-{}'.format(i) + ) + for i in range(10) + ]), + html.Div(id='container'), + dcc.RadioItems() + ]) + + @app.callback( + Output('container', 'children'), + [Input('input-{}'.format(i), 'value') for i in range(10)]) + def dynamic_output(*args): + call_count.value += 1 + return json.dumps(args, indent=2) + + self.startServer(app) + + time.sleep(5) + + self.percy_runner.snapshot(name='layout') + + self.assertEqual(call_count.value, 1) + + + def test_rendering_new_content_calls_callback_once_per_output(self): app = Dash(__name__) call_count = Value('i', 0) From db4f0cc5ce6849a0a92a2db7f855aa6dd7b571ae Mon Sep 17 00:00:00 2001 From: chriddyp Date: Fri, 15 Sep 2017 15:26:52 -0400 Subject: [PATCH 04/21] add percy names --- tests/test_render.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_render.py b/tests/test_render.py index 9cf22cd..4a11b7c 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -1445,7 +1445,9 @@ def dynamic_output(*args): time.sleep(5) - self.percy_runner.snapshot(name='layout') + self.percy_runner.snapshot( + name='test_rendering_layout_calls_callback_once_per_output' + ) self.assertEqual(call_count.value, 1) @@ -1497,6 +1499,8 @@ def dynamic_output(*args): time.sleep(5) - self.percy_runner.snapshot(name='layout') + self.percy_runner.snapshot( + name='test_rendering_new_content_calls_callback_once_per_output' + ) self.assertEqual(call_count.value, 1) From 14e8b5fa0c2e8ad1d38072dd85541ad6f5ea125d Mon Sep 17 00:00:00 2001 From: chriddyp Date: Fri, 15 Sep 2017 16:37:05 -0400 Subject: [PATCH 05/21] :zap: remove print statements --- tests/test_render.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_render.py b/tests/test_render.py index 4a11b7c..27d19ce 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -1451,8 +1451,6 @@ def dynamic_output(*args): self.assertEqual(call_count.value, 1) - - def test_rendering_new_content_calls_callback_once_per_output(self): app = Dash(__name__) call_count = Value('i', 0) @@ -1472,7 +1470,6 @@ def test_rendering_new_content_calls_callback_once_per_output(self): Output('container', 'children'), [Input('display-content', 'n_clicks')]) def display_output(n_clicks): - print('display_output ' + str(n_clicks)) if n_clicks == 0: return '' return html.Div([ From 229732439cbd6f2df8b2e5a38562f82bb7e735fd Mon Sep 17 00:00:00 2001 From: chriddyp Date: Fri, 15 Sep 2017 16:38:47 -0400 Subject: [PATCH 06/21] filter out redundant input updates --- src/actions/index.js | 94 +++++++++++++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 32 deletions(-) diff --git a/src/actions/index.js b/src/actions/index.js index 956334f..da2a75f 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -7,12 +7,15 @@ import { isEmpty, keys, lensPath, + pluck, reject, + slice, sort, type, union, view } from 'ramda'; +import R from 'ramda'; import {createAction} from 'redux-actions'; import {crawlLayout, hasId} from '../reducers/utils'; import {APP_STATES} from '../reducers/constants'; @@ -40,10 +43,10 @@ function triggerDefaultState(dispatch, getState) { const {graphs} = getState(); const {InputGraph} = graphs; const allNodes = InputGraph.overallOrder(); + const inputNodeIds = []; allNodes.reverse(); allNodes.forEach(nodeId => { - const [componentId, componentProp] = nodeId.split('.'); - + const componentId = nodeId.split('.')[0]; /* * Filter out the outputs, * inputs that aren't leaves, @@ -53,24 +56,28 @@ function triggerDefaultState(dispatch, getState) { InputGraph.dependantsOf(nodeId).length == 0 && has(componentId, getState().paths) ) { + inputNodeIds.push(nodeId); + } + }); - // Get the initial property - const propLens = lensPath( - concat(getState().paths[componentId], - ['props', componentProp] - )); - const propValue = view( - propLens, - getState().layout - ); - - dispatch(notifyObservers({ - id: componentId, - props: {[componentProp]: propValue} - })); + reduceInputIds(inputNodeIds, InputGraph).forEach(nodeId => { + const [componentId, componentProp] = nodeId.split('.'); + // Get the initial property + const propLens = lensPath( + concat(getState().paths[componentId], + ['props', componentProp] + )); + const propValue = view( + propLens, + getState().layout + ); - } + dispatch(notifyObservers({ + id: componentId, + props: {[componentProp]: propValue} + })); }); + } export function redo() { @@ -116,6 +123,31 @@ export function undo() { +function reduceInputIds(nodeIds, InputGraph) { + /* + * Create input-output(s) pairs, + * sort by number of outputs, + * and remove redudant inputs (inputs that update the same output) + */ + const inputOutputPairs = nodeIds.map(nodeId => ({ + input: nodeId, + outputs: InputGraph.dependenciesOf(nodeId) + })); + + const sortedInputOutputPairs = sort( + (a, b) => b.outputs.length - a.outputs.length, + inputOutputPairs + ); + + const uniquePairs = sortedInputOutputPairs.filter((pair, i) => !contains( + pair.outputs, + pluck('outputs', slice(i + 1, Infinity, sortedInputOutputPairs)) + )); + + return pluck('input', uniquePairs); +} + + export function notifyObservers(payload) { return function (dispatch, getState) { @@ -136,7 +168,6 @@ export function notifyObservers(payload) { const {EventGraph, InputGraph} = graphs; /* - * Figure out all of the output id's that depend on this * event or input. * This includes id's that are direct children as well as @@ -394,7 +425,7 @@ export function notifyObservers(payload) { * We don't need to do this - just need * to compute the subtree */ - const newProps = []; + const newProps = {}; crawlLayout( observerUpdatePayload.props.children, function appendIds(child) { @@ -404,7 +435,7 @@ export function notifyObservers(payload) { `${child.props.id}.${childProp}` ); if (has(inputId, InputGraph.nodes)) { - newProps.push({ + newProps[inputId] = ({ id: child.props.id, props: { [childProp]: child.props[childProp] @@ -416,21 +447,20 @@ export function notifyObservers(payload) { } ); + /* + * Organize props by shared outputs so that we + * only make one request per output component + * (even if there are multiple inputs). + */ + const reducedNodeIds = reduceInputIds( + keys(newProps), InputGraph); const depOrder = InputGraph.overallOrder(); const sortedNewProps = sort((a, b) => - depOrder.indexOf(a.id) - depOrder.indexOf(b.id), - newProps + depOrder.indexOf(a) - depOrder.indexOf(b), + reducedNodeIds ); - - /* - * TODO - As in the case of Jack Luo's indicator app, - * all of these inputs could update a _single_ output. - * If that is the case, then we can collect all of their - * values and make a single request instead of making a - * different request for each input - */ - sortedNewProps.forEach(function(propUpdate) { - dispatch(notifyObservers(propUpdate)); + sortedNewProps.forEach(function(nodeId) { + dispatch(notifyObservers(newProps[nodeId])); }); } From cf735991e5ccabba60644d6658331c025666a051 Mon Sep 17 00:00:00 2001 From: chriddyp Date: Fri, 15 Sep 2017 16:41:28 -0400 Subject: [PATCH 07/21] :zap: unused import --- src/actions/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/actions/index.js b/src/actions/index.js index da2a75f..6da1100 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -15,7 +15,6 @@ import { union, view } from 'ramda'; -import R from 'ramda'; import {createAction} from 'redux-actions'; import {crawlLayout, hasId} from '../reducers/utils'; import {APP_STATES} from '../reducers/constants'; From 7c7e82cd17bfc6b37ea2928581416082ed48c0de Mon Sep 17 00:00:00 2001 From: chriddyp Date: Fri, 15 Sep 2017 16:45:58 -0400 Subject: [PATCH 08/21] v0.10.0-rc1 --- dash_renderer/__init__.py | 4 ++-- dash_renderer/version.py | 2 +- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dash_renderer/__init__.py b/dash_renderer/__init__.py index d9005d0..0b15ec2 100644 --- a/dash_renderer/__init__.py +++ b/dash_renderer/__init__.py @@ -30,9 +30,9 @@ { 'relative_package_path': 'bundle.js', "external_url": ( - 'https://unpkg.com/dash-renderer@{}' + 'https://unpkg.com/dash-renderer@0.10.0-rc1' '/dash_renderer/bundle.js' - ).format(__version__), + ), 'namespace': 'dash_renderer' } ] diff --git a/dash_renderer/version.py b/dash_renderer/version.py index e4e49b3..366d751 100644 --- a/dash_renderer/version.py +++ b/dash_renderer/version.py @@ -1 +1 @@ -__version__ = '0.9.0' +__version__ = '0.10.0rc1' diff --git a/package.json b/package.json index 0ae02e3..d17b6fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dash-renderer", - "version": "0.9.0", + "version": "0.10.0-rc1", "description": "render dash components in react", "main": "src/index.js", "scripts": { From 6b7ba4735ebe7fd46ce4d1a205d78acfa99cc96a Mon Sep 17 00:00:00 2001 From: chriddyp Date: Mon, 18 Sep 2017 18:12:36 -0400 Subject: [PATCH 09/21] shorten tests by only testing p27 and p36 --- circle.yml | 5 +---- tox.ini | 23 +---------------------- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/circle.yml b/circle.yml index e82ee45..2d1ff8a 100644 --- a/circle.yml +++ b/circle.yml @@ -2,12 +2,9 @@ machine: node: version: 6.9.2 post: - - pyenv global 2.7.10 3.3.6 3.4.4 3.5.3 3.6.2 + - pyenv global 2.7.10 3.6.2 environment: TOX_PYTHON_27: python2.7 - TOX_PYTHON_33: python3.3 - TOX_PYTHON_34: python3.4 - TOX_PYTHON_35: python3.5 TOX_PYTHON_36: python3.6 diff --git a/tox.ini b/tox.ini index 1462ab1..b642855 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,py33,py34,py35 +envlist = py27,py36 [testenv] deps = -rdev-requirements.txt @@ -13,27 +13,6 @@ commands = python -m unittest tests.test_render.Tests python -m unittest tests.test_race_conditions.Tests -[testenv:py33] -basepython={env:TOX_PYTHON_33} -commands = - python --version - python -m unittest tests.test_render.Tests - python -m unittest tests.test_race_conditions.Tests - -[testenv:py34] -basepython={env:TOX_PYTHON_34} -commands = - python --version - python -m unittest tests.test_render.Tests - python -m unittest tests.test_race_conditions.Tests - -[testenv:py35] -basepython={env:TOX_PYTHON_35} -commands = - python --version - python -m unittest tests.test_render.Tests - python -m unittest tests.test_race_conditions.Tests - [testenv:py36] basepython={env:TOX_PYTHON_36} commands = From 97558c5cef7f017c87e2dba1e8da9faa2ecdbc27 Mon Sep 17 00:00:00 2001 From: chriddyp Date: Mon, 18 Sep 2017 22:50:16 -0400 Subject: [PATCH 10/21] try combining all snapshots into a single build following instructions from https://percy.io/docs/learn/parallel-tests and http://tox.readthedocs.io/en/latest/example/basic.html#setting-environme nt-variables --- tox.ini | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index b642855..55ccf5a 100644 --- a/tox.ini +++ b/tox.ini @@ -7,14 +7,22 @@ deps = -rdev-requirements.txt passenv = * [testenv:py27] -basepython={env:TOX_PYTHON_27} +basepython = + {env:TOX_PYTHON_27} +setenv = + PERCY_PARALLEL_NONCE=$CIRCLE_BUILD_NUM + PERCY_PARALLEL_TOTAL=4 commands = python --version python -m unittest tests.test_render.Tests python -m unittest tests.test_race_conditions.Tests [testenv:py36] -basepython={env:TOX_PYTHON_36} +basepython = + {env:TOX_PYTHON_36} +setenv = + PERCY_PARALLEL_NONCE=$CIRCLE_BUILD_NUM + PERCY_PARALLEL_TOTAL=4 commands = python --version python -m unittest tests.test_render.Tests From efd83178efb18db64a0bfcdf3de3871631cf57fb Mon Sep 17 00:00:00 2001 From: chriddyp Date: Mon, 18 Sep 2017 23:28:46 -0400 Subject: [PATCH 11/21] print environ variables --- tests/IntegrationTests.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/IntegrationTests.py b/tests/IntegrationTests.py index 4951e40..347f498 100644 --- a/tests/IntegrationTests.py +++ b/tests/IntegrationTests.py @@ -9,12 +9,17 @@ import percy import time import unittest +import os class IntegrationTests(unittest.TestCase): @classmethod def setUpClass(cls): + print('PERCY_PARALLEL_NONCE') + print(os.environ['PERCY_PARALLEL_NONCE']) + print('PERCY_PARALLEL_TOTAL') + print(os.environ['PERCY_PARALLEL_TOTAL']) super(IntegrationTests, cls).setUpClass() cls.driver = webdriver.Chrome() From 769baa6aba93a06ad5949d15f46195ac3fd51974 Mon Sep 17 00:00:00 2001 From: chriddyp Date: Mon, 18 Sep 2017 23:31:51 -0400 Subject: [PATCH 12/21] centralize percy calls and add python version to their names --- tests/IntegrationTests.py | 6 ++++++ tests/test_render.py | 28 ++++++++++++++-------------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/tests/IntegrationTests.py b/tests/IntegrationTests.py index 347f498..c6c65b2 100644 --- a/tests/IntegrationTests.py +++ b/tests/IntegrationTests.py @@ -10,10 +10,16 @@ import time import unittest import os +import sys class IntegrationTests(unittest.TestCase): + def percy_snapshot(name=''): + self.percy_snapshot( + name='{} - {}'.format(name, sys.version_info) + ) + @classmethod def setUpClass(cls): print('PERCY_PARALLEL_NONCE') diff --git a/tests/test_render.py b/tests/test_render.py index 27d19ce..093313d 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -392,7 +392,7 @@ def test_initial_state(self): [] ) - self.percy_runner.snapshot(name='layout') + self.percy_snapshot(name='layout') assert_clean_console(self) @@ -424,7 +424,7 @@ def update_output(value): output1 = self.wait_for_element_by_id('output-1') wait_for(lambda: output1.text == 'initial value') - self.percy_runner.snapshot(name='simple-callback-1') + self.percy_snapshot(name='simple-callback-1') input1 = self.wait_for_element_by_id('input') input1.clear() @@ -433,7 +433,7 @@ def update_output(value): output1 = lambda: self.wait_for_element_by_id('output-1') wait_for(lambda: output1().text == 'hello world') - self.percy_runner.snapshot(name='simple-callback-2') + self.percy_snapshot(name='simple-callback-2') self.assertEqual( call_count.value, @@ -511,7 +511,7 @@ def update_input(value): '''.replace('\n', '').replace(' ', '') ) ) - self.percy_runner.snapshot(name='callback-generating-function-1') + self.percy_snapshot(name='callback-generating-function-1') # the paths should include these new output IDs self.assertEqual( @@ -551,7 +551,7 @@ def update_input(value): ), [] ) - self.percy_runner.snapshot(name='callback-generating-function-2') + self.percy_snapshot(name='callback-generating-function-2') assert_clean_console(self) @@ -767,7 +767,7 @@ def chapter1_assertions(): generic_chapter_assertions('chapter1') chapter1_assertions() - self.percy_runner.snapshot(name='chapter-1') + self.percy_snapshot(name='chapter-1') # switch chapters (self.driver.find_elements_by_css_selector( @@ -776,7 +776,7 @@ def chapter1_assertions(): # sleep just to make sure that no calls happen after our check time.sleep(2) - self.percy_runner.snapshot(name='chapter-2') + self.percy_snapshot(name='chapter-2') wait_for(lambda: call_counts['body'].value == 2) wait_for(lambda: call_counts['chapter2-graph'].value == 1) wait_for(lambda: call_counts['chapter2-label'].value == 1) @@ -821,7 +821,7 @@ def chapter2_assertions(): )[2]).click() # sleep just to make sure that no calls happen after our check time.sleep(2) - self.percy_runner.snapshot(name='chapter-3') + self.percy_snapshot(name='chapter-3') wait_for(lambda: call_counts['body'].value == 3) wait_for(lambda: call_counts['chapter3-graph'].value == 1) wait_for(lambda: call_counts['chapter3-label'].value == 1) @@ -874,7 +874,7 @@ def chapter3_assertions(): self.driver.find_element_by_id('body').text == 'Just a string' )) - self.percy_runner.snapshot(name='chapter-4') + self.percy_snapshot(name='chapter-4') # each element should exist in the dom paths = self.driver.execute_script( @@ -893,7 +893,7 @@ def chapter3_assertions(): )[0]).click() time.sleep(0.5) chapter1_assertions() - self.percy_runner.snapshot(name='chapter-1-again') + self.percy_snapshot(name='chapter-1-again') # switch to 5 (self.driver.find_elements_by_css_selector( @@ -911,7 +911,7 @@ def chapter3_assertions(): chapter5_button().click() wait_for(lambda: chapter5_div().text == chapter5_output_children) time.sleep(0.5) - self.percy_runner.snapshot(name='chapter-5') + self.percy_snapshot(name='chapter-5') self.assertEqual(call_counts['chapter5-output'].value, 1) def test_dependencies_on_components_that_dont_exist(self): @@ -946,7 +946,7 @@ def update_output_2(value): el = self.wait_for_element_by_id('output-1') wait_for(lambda: el.text == 'initial value') - self.percy_runner.snapshot(name='dependencies') + self.percy_snapshot(name='dependencies') time.sleep(1.0) self.assertEqual(output_1_call_count.value, 1) self.assertEqual(output_2_call_count.value, 0) @@ -1445,7 +1445,7 @@ def dynamic_output(*args): time.sleep(5) - self.percy_runner.snapshot( + self.percy_snapshot( name='test_rendering_layout_calls_callback_once_per_output' ) @@ -1496,7 +1496,7 @@ def dynamic_output(*args): time.sleep(5) - self.percy_runner.snapshot( + self.percy_snapshot( name='test_rendering_new_content_calls_callback_once_per_output' ) From cf2b282994d60ebd917da8e7333a23c0e4f122b7 Mon Sep 17 00:00:00 2001 From: chriddyp Date: Mon, 18 Sep 2017 23:32:21 -0400 Subject: [PATCH 13/21] only 2 sets of calls - one for each python version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `test_race_condtions` doesn’t make any snapshots --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 55ccf5a..11d5f6c 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ basepython = {env:TOX_PYTHON_27} setenv = PERCY_PARALLEL_NONCE=$CIRCLE_BUILD_NUM - PERCY_PARALLEL_TOTAL=4 + PERCY_PARALLEL_TOTAL=2 commands = python --version python -m unittest tests.test_render.Tests @@ -22,7 +22,7 @@ basepython = {env:TOX_PYTHON_36} setenv = PERCY_PARALLEL_NONCE=$CIRCLE_BUILD_NUM - PERCY_PARALLEL_TOTAL=4 + PERCY_PARALLEL_TOTAL=2 commands = python --version python -m unittest tests.test_render.Tests From 4a8ce53663071dd2835909e7c55da2d2c07e5694 Mon Sep 17 00:00:00 2001 From: chriddyp Date: Mon, 18 Sep 2017 23:42:59 -0400 Subject: [PATCH 14/21] fix syntax --- tests/IntegrationTests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/IntegrationTests.py b/tests/IntegrationTests.py index c6c65b2..5efa32a 100644 --- a/tests/IntegrationTests.py +++ b/tests/IntegrationTests.py @@ -15,8 +15,8 @@ class IntegrationTests(unittest.TestCase): - def percy_snapshot(name=''): - self.percy_snapshot( + def percy_snapshot(cls, name=''): + cls.percy_runner.snapshot( name='{} - {}'.format(name, sys.version_info) ) From 5899dbb22993c1fd61626bbe1c92b031d42c18f6 Mon Sep 17 00:00:00 2001 From: chriddyp Date: Mon, 18 Sep 2017 23:56:10 -0400 Subject: [PATCH 15/21] env variables in tox :see_no_evil: --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 11d5f6c..a5458ae 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ passenv = * basepython = {env:TOX_PYTHON_27} setenv = - PERCY_PARALLEL_NONCE=$CIRCLE_BUILD_NUM + PERCY_PARALLEL_NONCE={CIRCLE_BUILD_NUM} PERCY_PARALLEL_TOTAL=2 commands = python --version @@ -21,7 +21,7 @@ commands = basepython = {env:TOX_PYTHON_36} setenv = - PERCY_PARALLEL_NONCE=$CIRCLE_BUILD_NUM + PERCY_PARALLEL_NONCE={CIRCLE_BUILD_NUM} PERCY_PARALLEL_TOTAL=2 commands = python --version From 7cd066e8d78b0ad7ff43cd70b8fcfdac92615133 Mon Sep 17 00:00:00 2001 From: chriddyp Date: Tue, 19 Sep 2017 00:09:31 -0400 Subject: [PATCH 16/21] try setting the env variable this way --- circle.yml | 4 +++- tox.ini | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/circle.yml b/circle.yml index 2d1ff8a..05f4348 100644 --- a/circle.yml +++ b/circle.yml @@ -1,4 +1,7 @@ machine: + pre: + - echo export PERCY_PARALLEL_NONCE=$CIRCLE_BUILD_NUM >> $HOME/.circlerc + node: version: 6.9.2 post: @@ -7,7 +10,6 @@ machine: TOX_PYTHON_27: python2.7 TOX_PYTHON_36: python3.6 - dependencies: pre: - pip install tox diff --git a/tox.ini b/tox.ini index a5458ae..59cb376 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,6 @@ passenv = * basepython = {env:TOX_PYTHON_27} setenv = - PERCY_PARALLEL_NONCE={CIRCLE_BUILD_NUM} PERCY_PARALLEL_TOTAL=2 commands = python --version @@ -21,7 +20,6 @@ commands = basepython = {env:TOX_PYTHON_36} setenv = - PERCY_PARALLEL_NONCE={CIRCLE_BUILD_NUM} PERCY_PARALLEL_TOTAL=2 commands = python --version From bb28957d0fc69058d4c1ccb787219021201f91e6 Mon Sep 17 00:00:00 2001 From: chriddyp Date: Tue, 19 Sep 2017 00:27:47 -0400 Subject: [PATCH 17/21] log snapshot name --- tests/IntegrationTests.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/IntegrationTests.py b/tests/IntegrationTests.py index 5efa32a..de4f977 100644 --- a/tests/IntegrationTests.py +++ b/tests/IntegrationTests.py @@ -16,8 +16,10 @@ class IntegrationTests(unittest.TestCase): def percy_snapshot(cls, name=''): + snapshot_name = '{} - {}'.format(name, sys.version_info) + print(snapshot_name) cls.percy_runner.snapshot( - name='{} - {}'.format(name, sys.version_info) + name=snapshot_name ) @classmethod From 82ba1fe649fc95ce6dc2307245c8e3bd6e0f0698 Mon Sep 17 00:00:00 2001 From: chriddyp Date: Tue, 19 Sep 2017 00:30:34 -0400 Subject: [PATCH 18/21] try starting and finishing at beginning and end of test suite --- tests/IntegrationTests.py | 7 ++++++- tox.ini | 2 -- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/IntegrationTests.py b/tests/IntegrationTests.py index de4f977..4139a6c 100644 --- a/tests/IntegrationTests.py +++ b/tests/IntegrationTests.py @@ -36,12 +36,17 @@ def setUpClass(cls): ) cls.percy_runner = percy.Runner(loader=loader) - cls.percy_runner.initialize_build() + if sys.version_info.major == 2: + cls.percy_runner.initialize_build() + @classmethod def tearDownClass(cls): super(IntegrationTests, cls).tearDownClass() cls.driver.quit() + if sys.version_info.major == 3: + cls.percy_runner.initialize_build() + cls.percy_runner.finalize_build() def setUp(s): diff --git a/tox.ini b/tox.ini index 59cb376..6a5e949 100644 --- a/tox.ini +++ b/tox.ini @@ -9,8 +9,6 @@ passenv = * [testenv:py27] basepython = {env:TOX_PYTHON_27} -setenv = - PERCY_PARALLEL_TOTAL=2 commands = python --version python -m unittest tests.test_render.Tests From ab6da7645dd6e0fda628fed4ac3f5ac38f14a8eb Mon Sep 17 00:00:00 2001 From: chriddyp Date: Tue, 19 Sep 2017 00:45:39 -0400 Subject: [PATCH 19/21] rm print --- tests/IntegrationTests.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/IntegrationTests.py b/tests/IntegrationTests.py index 4139a6c..ac11085 100644 --- a/tests/IntegrationTests.py +++ b/tests/IntegrationTests.py @@ -26,8 +26,6 @@ def percy_snapshot(cls, name=''): def setUpClass(cls): print('PERCY_PARALLEL_NONCE') print(os.environ['PERCY_PARALLEL_NONCE']) - print('PERCY_PARALLEL_TOTAL') - print(os.environ['PERCY_PARALLEL_TOTAL']) super(IntegrationTests, cls).setUpClass() cls.driver = webdriver.Chrome() From fc17e667fb3cd457416a1dc86a03ec2af0241d50 Mon Sep 17 00:00:00 2001 From: chriddyp Date: Tue, 19 Sep 2017 09:19:33 -0400 Subject: [PATCH 20/21] typo --- tests/IntegrationTests.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/IntegrationTests.py b/tests/IntegrationTests.py index ac11085..3ca54f3 100644 --- a/tests/IntegrationTests.py +++ b/tests/IntegrationTests.py @@ -43,9 +43,7 @@ def tearDownClass(cls): super(IntegrationTests, cls).tearDownClass() cls.driver.quit() if sys.version_info.major == 3: - cls.percy_runner.initialize_build() - - cls.percy_runner.finalize_build() + cls.percy_runner.finalize_build() def setUp(s): pass From c504798886f218f24ed7029da3105ab78803956f Mon Sep 17 00:00:00 2001 From: chriddyp Date: Tue, 19 Sep 2017 10:28:25 -0400 Subject: [PATCH 21/21] 4 parallel tests 1 for each subclass (even if no snapshots were made) --- tests/IntegrationTests.py | 8 ++++---- tox.ini | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/IntegrationTests.py b/tests/IntegrationTests.py index 3ca54f3..2730336 100644 --- a/tests/IntegrationTests.py +++ b/tests/IntegrationTests.py @@ -26,6 +26,8 @@ def percy_snapshot(cls, name=''): def setUpClass(cls): print('PERCY_PARALLEL_NONCE') print(os.environ['PERCY_PARALLEL_NONCE']) + print('PERCY_PARALLEL_TOTAL') + print(os.environ['PERCY_PARALLEL_TOTAL']) super(IntegrationTests, cls).setUpClass() cls.driver = webdriver.Chrome() @@ -34,16 +36,14 @@ def setUpClass(cls): ) cls.percy_runner = percy.Runner(loader=loader) - if sys.version_info.major == 2: - cls.percy_runner.initialize_build() + cls.percy_runner.initialize_build() @classmethod def tearDownClass(cls): super(IntegrationTests, cls).tearDownClass() cls.driver.quit() - if sys.version_info.major == 3: - cls.percy_runner.finalize_build() + cls.percy_runner.finalize_build() def setUp(s): pass diff --git a/tox.ini b/tox.ini index 6a5e949..d448499 100644 --- a/tox.ini +++ b/tox.ini @@ -9,6 +9,8 @@ passenv = * [testenv:py27] basepython = {env:TOX_PYTHON_27} +setenv = + PERCY_PARALLEL_TOTAL=4 commands = python --version python -m unittest tests.test_render.Tests @@ -18,7 +20,7 @@ commands = basepython = {env:TOX_PYTHON_36} setenv = - PERCY_PARALLEL_TOTAL=2 + PERCY_PARALLEL_TOTAL=4 commands = python --version python -m unittest tests.test_render.Tests