<commit_before># coding: utf-8
import json
import os
import time

from graphite_api._vendor import whisper

from . import TestCase, WHISPER_DIR

try:
    from flask.ext.cache import Cache
except ImportError:
    Cache = None


class RenderTest(TestCase):
    db = os.path.join(WHISPER_DIR, 'test.wsp')
    url = '/render'

    def create_db(self):
        whisper.create(self.db, [(1, 60)])

        self.ts = int(time.time())
        whisper.update(self.db, 0.5, self.ts - 2)
        whisper.update(self.db, 0.4, self.ts - 1)
        whisper.update(self.db, 0.6, self.ts)

    def test_render_view(self):
        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'format': 'json'})
        self.assertEqual(json.loads(response.data.decode('utf-8')), [])

        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'format': 'pdf'})
        self.assertEqual(response.headers['Content-Type'], 'application/x-pdf')

        response = self.app.get(self.url, query_string={'target': 'test'})
        self.assertEqual(response.headers['Content-Type'], 'image/png')

        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'format': 'dygraph'})
        self.assertEqual(json.loads(response.data.decode('utf-8')), {})

        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'format': 'rickshaw'})
        self.assertEqual(json.loads(response.data.decode('utf-8')), [])

        self.create_db()
        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'format': 'json'})
        data = json.loads(response.data.decode('utf-8'))
        end = data[0]['datapoints'][-4:]
        try:
            self.assertEqual(
                end, [[None, self.ts - 3], [0.5, self.ts - 2],
                      [0.4, self.ts - 1], [0.6, self.ts]])
        except AssertionError:
            self.assertEqual(
                end, [[0.5, self.ts - 2], [0.4, self.ts - 1],
                      [0.6, self.ts], [None, self.ts + 1]])

        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'maxDataPoints': 2,
                                                        'format': 'json'})
        data = json.loads(response.data.decode('utf-8'))
        # 1 is a time race cond
        self.assertTrue(len(data[0]['datapoints']) in [1, 2])

        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'maxDataPoints': 200,
                                                        'format': 'json'})
        data = json.loads(response.data.decode('utf-8'))
        # 59 is a time race cond
        self.assertTrue(len(data[0]['datapoints']) in [59, 60])

        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'format': 'dygraph'})
        data = json.loads(response.data.decode('utf-8'))
        end = data['data'][-4:]
        try:
            self.assertEqual(
                end, [[(self.ts - 3) * 1000, None],
                      [(self.ts - 2) * 1000, 0.5],
                      [(self.ts - 1) * 1000, 0.4],
                      [self.ts * 1000, 0.6]])
        except AssertionError:
            self.assertEqual(
                end, [[(self.ts - 2) * 1000, 0.5],
                      [(self.ts - 1) * 1000, 0.4],
                      [self.ts * 1000, 0.6],
                      [(self.ts + 1) * 1000, None]])

        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'format': 'rickshaw'})
        data = json.loads(response.data.decode('utf-8'))
        end = data[0]['datapoints'][-4:]
        try:
            self.assertEqual(
                end, [{'x': self.ts - 3, 'y': None},
                      {'x': self.ts - 2, 'y': 0.5},
                      {'x': self.ts - 1, 'y': 0.4},
                      {'x': self.ts, 'y': 0.6}])
        except AssertionError:
            self.assertEqual(
                end, [{'x': self.ts - 2, 'y': 0.5},
                      {'x': self.ts - 1, 'y': 0.4},
                      {'x': self.ts, 'y': 0.6},
                      {'x': self.ts + 1, 'y': None}])

    def test_render_constant_line(self):
        response = self.app.get(self.url, query_string={
            'target': 'constantLine(12)'})
        self.assertEqual(response.headers['Content-Type'], 'image/png')

        response = self.app.get(self.url, query_string={
            'target': 'constantLine(12)', 'format': 'json'})
        data = json.loads(response.data.decode('utf-8'))[0]['datapoints']
        self.assertEqual(len(data), 3)
        for point, ts in data:
            self.assertEqual(point, 12)

        response = self.app.get(self.url, query_string={
            'target': 'constantLine(12)', 'format': 'json',
            'maxDataPoints': 12})
        data = json.loads(response.data.decode('utf-8'))[0]['datapoints']
        self.assertEqual(len(data), 3)
        for point, ts in data:
            self.assertEqual(point, 12)

    def test_float_maxdatapoints(self):
        response = self.app.get(self.url, query_string={
            'target': 'sin("foo")', 'format': 'json',
            'maxDataPoints': 5.5})  # rounded to int
        data = json.loads(response.data.decode('utf-8'))[0]['datapoints']
        self.assertEqual(len(data), 5)

    def test_constantline_pathexpr(self):
        response = self.app.get(self.url, query_string={
            'target': 'sumSeries(constantLine(12), constantLine(5))',
            'format': 'json',
        })
        data = json.loads(response.data.decode('utf-8'))[0]['datapoints']
        self.assertEqual([d[0] for d in data], [17, 17, 17])

    def test_area_between(self):
        response = self.app.get(self.url, query_string={
            'target': ['areaBetween(sin("foo"), sin("bar", 2))'],
            'format': 'json',
        })
        data = json.loads(response.data.decode('utf-8'))
        self.assertEqual(len(data), 2)

    def test_sumseries(self):
        response = self.app.get(self.url, query_string={
            'target': ['sumSeries(sin("foo"), sin("bar", 2))',
                       'sin("baz", 3)'],
            'format': 'json',
        })
        data = json.loads(response.data.decode('utf-8'))
        agg = {}
        for series in data:
            agg[series['target']] = series['datapoints']
        for index, value in enumerate(agg['baz']):
            self.assertEqual(value, agg['sumSeries(sin(bar),sin(foo))'][index])

        response = self.app.get(self.url, query_string={
            'target': ['sumSeries(sin("foo"), sin("bar", 2))',
                       'sin("baz", 3)'],
            'format': 'json',
            'maxDataPoints': 100,
        })
        data = json.loads(response.data.decode('utf-8'))
        agg = {}
        for series in data:
            self.assertTrue(len(series['datapoints']) <= 100)
            agg[series['target']] = series['datapoints']
        for index, value in enumerate(agg['baz']):
            self.assertEqual(value, agg['sumSeries(sin(bar),sin(foo))'][index])

    def test_correct_timezone(self):
        response = self.app.get(self.url, query_string={
            'target': 'constantLine(12)',
            'format': 'json',
            'from': '07:00_20140226',
            'until': '08:00_20140226',
            # tz is UTC
        })
        data = json.loads(response.data.decode('utf-8'))[0]['datapoints']

        # all the from/until/tz combinations lead to the same window
        expected = [[12, 1393398000], [12, 1393399800], [12, 1393401600]]
        self.assertEqual(data, expected)

        response = self.app.get(self.url, query_string={
            'target': 'constantLine(12)',
            'format': 'json',
            'from': '08:00_20140226',
            'until': '09:00_20140226',
            'tz': 'Europe/Berlin',
        })
        data = json.loads(response.data.decode('utf-8'))[0]['datapoints']
        self.assertEqual(data, expected)

    def test_render_options(self):
        self.create_db()
        db2 = os.path.join(WHISPER_DIR, 'foo.wsp')
        whisper.create(db2, [(1, 60)])
        ts = int(time.time())
        whisper.update(db2, 0.5, ts - 2)

        for qs in [
            {'logBase': 'e'},
            {'logBase': 1},
            {'logBase': 0.5},
            {'margin': -1},
            {'colorList': 'orange,green,blue,#0f0'},
            {'bgcolor': 'orange'},
            {'bgcolor': 'aaabbb'},
            {'bgcolor': '#aaabbb'},
            {'bgcolor': '#aaabbbff'},
            {'fontBold': 'true'},
            {'title': 'Hellò'},
            {'title': 'true'},
            {'vtitle': 'Hellò'},
            {'title': 'Hellò', 'yAxisSide': 'right'},
            {'uniqueLegend': 'true', '_expr': 'secondYAxis({0})'},
            {'uniqueLegend': 'true', 'vtitleRight': 'foo',
             '_expr': 'secondYAxis({0})'},
            {'graphOnly': 'true', 'yUnitSystem': 'si'},
            {'lineMode': 'staircase'},
            {'lineMode': 'slope'},
            {'lineMode': 'connected'},
            {'min': 1, 'max': 2, 'thickness': 2, 'yUnitSystem': 'none'},
            {'yMax': 5, 'yLimit': 0.5, 'yStep': 0.1},
            {'yMax': 'max', 'yUnitSystem': 'binary'},
            {'areaMode': 'stacked', '_expr': 'stacked({0})'},
            {'lineMode': 'staircase', '_expr': 'stacked({0})'},
            {'areaMode': 'first', '_expr': 'stacked({0})'},
            {'areaMode': 'all', '_expr': 'stacked({0})'},
            {'areaMode': 'stacked', 'areaAlpha': 0.5, '_expr': 'stacked({0})'},
            {'areaMode': 'stacked', 'areaAlpha': 'a', '_expr': 'stacked({0})'},
            {'_expr': 'dashed(lineWidth({0}, 5))'},
            {'target': 'areaBetween(*)'},
            {'drawNullAsZero': 'true'},
            {'_expr': 'drawAsInfinite({0})'},
            {'graphType': 'pie', 'pieMode': 'average', 'title': 'Pie'},
            {'graphType': 'pie', 'pieMode': 'average', 'hideLegend': 'true'},
            {'graphType': 'pie', 'pieMode': 'average', 'valueLabels': 'none'},
            {'graphType': 'pie', 'pieMode': 'average',
             'valueLabels': 'number'},
            {'graphType': 'pie', 'pieMode': 'average', 'pieLabels': 'rotated'},
            {'noCache': 'true'},
            {'cacheTimeout': 5},
            {'cacheTimeout': 5},  # cache hit
        ]:
            if qs.setdefault('target', ['foo', 'test']) == ['foo', 'test']:
                if '_expr' in qs:
                    expr = qs.pop('_expr')
                    qs['target'] = [expr.format(t) for t in qs['target']]
            response = self.app.get(self.url, query_string=qs)
            self.assertEqual(response.status_code, 200)
            self.assertEqual(response.headers['Content-Type'], 'image/png')
            if Cache is None or qs.get('noCache'):
                self.assertEqual(response.headers['Pragma'], 'no-cache')
                self.assertEqual(response.headers['Cache-Control'], 'no-cache')
                self.assertFalse('Expires' in response.headers)
            else:
                self.assertEqual(response.headers['Cache-Control'],
                                 'max-age={0}'.format(
                                     qs.get('cacheTimeout', 60)))
                self.assertNotEqual(response.headers['Cache-Control'],
                                    'no-cache')
                self.assertFalse('Pragma' in response.headers)

        for qs in [
            {'bgcolor': 'foo'},
        ]:
            qs['target'] = 'test'
            with self.assertRaises(ValueError):
                response = self.app.get(self.url, query_string=qs)

        for qs in [
            {'lineMode': 'stacked'},
        ]:
            qs['target'] = 'test'
            with self.assertRaises(AssertionError):
                response = self.app.get(self.url, query_string=qs)

    def test_render_validation(self):
        whisper.create(self.db, [(1, 60)])

        response = self.app.get(self.url)
        self.assertJSON(response, {'errors': {
            'target': 'This parameter is required.'}}, status_code=400)

        response = self.app.get(self.url, query_string={'graphType': 'foo',
                                                        'target': 'test'})
        self.assertJSON(response, {'errors': {
            'graphType': "Invalid graphType 'foo', must be one of 'line', "
            "'pie'."}}, status_code=400)

        response = self.app.get(self.url, query_string={'maxDataPoints': 'foo',
                                                        'target': 'test'})
        self.assertJSON(response, {'errors': {
            'maxDataPoints': 'Must be an integer.'}}, status_code=400)

        response = self.app.get(self.url, query_string={
            'from': '21:2020140313',
            'until': '21:2020140313',
            'target': 'test'})
        self.assertJSON(response, {'errors': {
            'from': 'Invalid empty time range',
            'until': 'Invalid empty time range',
        }}, status_code=400)

        response = self.app.get(self.url, query_string={
            'target': 'foo',
            'width': 100,
            'thickness': '1.5',
            'fontBold': 'true',
            'fontItalic': 'default',
        })
        self.assertEqual(response.status_code, 200)

        response = self.app.get(self.url, query_string={
            'target': 'foo', 'tz': 'Europe/Lausanne'})
        self.assertJSON(response, {'errors': {
            'tz': "Unknown timezone: 'Europe/Lausanne'.",
        }}, status_code=400)

        response = self.app.get(self.url, query_string={'target': 'test:aa',
                                                        'graphType': 'pie'})
        self.assertJSON(response, {'errors': {
            'target': "Invalid target: 'test:aa'.",
        }}, status_code=400)

        response = self.app.get(self.url, query_string={
            'target': ['test', 'foo:1.2'], 'graphType': 'pie'})
        self.assertEqual(response.status_code, 200)

        response = self.app.get(self.url, query_string={'target': ['test',
                                                                   '']})
        self.assertEqual(response.status_code, 200)

        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'format': 'csv'})
        lines = response.data.decode('utf-8').strip().split('\n')
        self.assertEqual(len(lines), 60)
        self.assertFalse(any([l.strip().split(',')[2] for l in lines]))

        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'format': 'svg',
                                                        'jsonp': 'foo'})
        jsonpsvg = response.data.decode('utf-8')
        self.assertTrue(jsonpsvg.startswith('foo("<?xml version=\\"1.0\\"'))
        self.assertTrue(jsonpsvg.endswith('</script>\\n</svg>")'))

        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'format': 'svg'})
        svg = response.data.decode('utf-8')
        self.assertTrue(svg.startswith('<?xml version="1.0"'))

        response = self.app.get(self.url, query_string={'target': 'inexisting',
                                                        'format': 'svg'})
        self.assertEqual(response.status_code, 200)
        svg = response.data.decode('utf-8')
        self.assertTrue(svg.startswith('<?xml version="1.0"'))

        response = self.app.get(self.url, query_string={
            'target': 'sum(test)',
        })
        self.assertEqual(response.status_code, 200)
<commit_msg>Another time-specific race condition that could fail<commit_after># coding: utf-8
import json
import os
import time

from graphite_api._vendor import whisper

from . import TestCase, WHISPER_DIR

try:
    from flask.ext.cache import Cache
except ImportError:
    Cache = None


class RenderTest(TestCase):
    db = os.path.join(WHISPER_DIR, 'test.wsp')
    url = '/render'

    def create_db(self):
        whisper.create(self.db, [(1, 60)])

        self.ts = int(time.time())
        whisper.update(self.db, 0.5, self.ts - 2)
        whisper.update(self.db, 0.4, self.ts - 1)
        whisper.update(self.db, 0.6, self.ts)

    def test_render_view(self):
        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'format': 'json'})
        self.assertEqual(json.loads(response.data.decode('utf-8')), [])

        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'format': 'pdf'})
        self.assertEqual(response.headers['Content-Type'], 'application/x-pdf')

        response = self.app.get(self.url, query_string={'target': 'test'})
        self.assertEqual(response.headers['Content-Type'], 'image/png')

        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'format': 'dygraph'})
        self.assertEqual(json.loads(response.data.decode('utf-8')), {})

        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'format': 'rickshaw'})
        self.assertEqual(json.loads(response.data.decode('utf-8')), [])

        self.create_db()
        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'format': 'json'})
        data = json.loads(response.data.decode('utf-8'))
        end = data[0]['datapoints'][-4:]
        try:
            self.assertEqual(
                end, [[None, self.ts - 3], [0.5, self.ts - 2],
                      [0.4, self.ts - 1], [0.6, self.ts]])
        except AssertionError:
            self.assertEqual(
                end, [[0.5, self.ts - 2], [0.4, self.ts - 1],
                      [0.6, self.ts], [None, self.ts + 1]])

        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'maxDataPoints': 2,
                                                        'format': 'json'})
        data = json.loads(response.data.decode('utf-8'))
        # 1 is a time race cond
        self.assertTrue(len(data[0]['datapoints']) in [1, 2])

        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'maxDataPoints': 200,
                                                        'format': 'json'})
        data = json.loads(response.data.decode('utf-8'))
        # 59 is a time race cond
        self.assertTrue(len(data[0]['datapoints']) in [59, 60])

        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'format': 'dygraph'})
        data = json.loads(response.data.decode('utf-8'))
        end = data['data'][-4:]
        try:
            self.assertEqual(
                end, [[(self.ts - 3) * 1000, None],
                      [(self.ts - 2) * 1000, 0.5],
                      [(self.ts - 1) * 1000, 0.4],
                      [self.ts * 1000, 0.6]])
        except AssertionError:
            self.assertEqual(
                end, [[(self.ts - 2) * 1000, 0.5],
                      [(self.ts - 1) * 1000, 0.4],
                      [self.ts * 1000, 0.6],
                      [(self.ts + 1) * 1000, None]])

        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'format': 'rickshaw'})
        data = json.loads(response.data.decode('utf-8'))
        end = data[0]['datapoints'][-4:]
        try:
            self.assertEqual(
                end, [{'x': self.ts - 3, 'y': None},
                      {'x': self.ts - 2, 'y': 0.5},
                      {'x': self.ts - 1, 'y': 0.4},
                      {'x': self.ts, 'y': 0.6}])
        except AssertionError:
            self.assertEqual(
                end, [{'x': self.ts - 2, 'y': 0.5},
                      {'x': self.ts - 1, 'y': 0.4},
                      {'x': self.ts, 'y': 0.6},
                      {'x': self.ts + 1, 'y': None}])

    def test_render_constant_line(self):
        response = self.app.get(self.url, query_string={
            'target': 'constantLine(12)'})
        self.assertEqual(response.headers['Content-Type'], 'image/png')

        response = self.app.get(self.url, query_string={
            'target': 'constantLine(12)', 'format': 'json'})
        data = json.loads(response.data.decode('utf-8'))[0]['datapoints']
        self.assertEqual(len(data), 3)
        for point, ts in data:
            self.assertEqual(point, 12)

        response = self.app.get(self.url, query_string={
            'target': 'constantLine(12)', 'format': 'json',
            'maxDataPoints': 12})
        data = json.loads(response.data.decode('utf-8'))[0]['datapoints']
        self.assertEqual(len(data), 3)
        for point, ts in data:
            self.assertEqual(point, 12)

    def test_float_maxdatapoints(self):
        response = self.app.get(self.url, query_string={
            'target': 'sin("foo")', 'format': 'json',
            'maxDataPoints': 5.5})  # rounded to int
        data = json.loads(response.data.decode('utf-8'))[0]['datapoints']
        self.assertEqual(len(data), 5)

    def test_constantline_pathexpr(self):
        response = self.app.get(self.url, query_string={
            'target': 'sumSeries(constantLine(12), constantLine(5))',
            'format': 'json',
        })
        data = json.loads(response.data.decode('utf-8'))[0]['datapoints']
        self.assertEqual([d[0] for d in data], [17, 17, 17])

    def test_area_between(self):
        response = self.app.get(self.url, query_string={
            'target': ['areaBetween(sin("foo"), sin("bar", 2))'],
            'format': 'json',
        })
        data = json.loads(response.data.decode('utf-8'))
        self.assertEqual(len(data), 2)

    def test_sumseries(self):
        response = self.app.get(self.url, query_string={
            'target': ['sumSeries(sin("foo"), sin("bar", 2))',
                       'sin("baz", 3)'],
            'format': 'json',
        })
        data = json.loads(response.data.decode('utf-8'))
        agg = {}
        for series in data:
            agg[series['target']] = series['datapoints']
        for index, value in enumerate(agg['baz']):
            self.assertEqual(value, agg['sumSeries(sin(bar),sin(foo))'][index])

        response = self.app.get(self.url, query_string={
            'target': ['sumSeries(sin("foo"), sin("bar", 2))',
                       'sin("baz", 3)'],
            'format': 'json',
            'maxDataPoints': 100,
        })
        data = json.loads(response.data.decode('utf-8'))
        agg = {}
        for series in data:
            self.assertTrue(len(series['datapoints']) <= 100)
            agg[series['target']] = series['datapoints']
        for index, value in enumerate(agg['baz']):
            self.assertEqual(value, agg['sumSeries(sin(bar),sin(foo))'][index])

    def test_correct_timezone(self):
        response = self.app.get(self.url, query_string={
            'target': 'constantLine(12)',
            'format': 'json',
            'from': '07:00_20140226',
            'until': '08:00_20140226',
            # tz is UTC
        })
        data = json.loads(response.data.decode('utf-8'))[0]['datapoints']

        # all the from/until/tz combinations lead to the same window
        expected = [[12, 1393398000], [12, 1393399800], [12, 1393401600]]
        self.assertEqual(data, expected)

        response = self.app.get(self.url, query_string={
            'target': 'constantLine(12)',
            'format': 'json',
            'from': '08:00_20140226',
            'until': '09:00_20140226',
            'tz': 'Europe/Berlin',
        })
        data = json.loads(response.data.decode('utf-8'))[0]['datapoints']
        self.assertEqual(data, expected)

    def test_render_options(self):
        self.create_db()
        db2 = os.path.join(WHISPER_DIR, 'foo.wsp')
        whisper.create(db2, [(1, 60)])
        ts = int(time.time())
        whisper.update(db2, 0.5, ts - 2)

        for qs in [
            {'logBase': 'e'},
            {'logBase': 1},
            {'logBase': 0.5},
            {'margin': -1},
            {'colorList': 'orange,green,blue,#0f0'},
            {'bgcolor': 'orange'},
            {'bgcolor': 'aaabbb'},
            {'bgcolor': '#aaabbb'},
            {'bgcolor': '#aaabbbff'},
            {'fontBold': 'true'},
            {'title': 'Hellò'},
            {'title': 'true'},
            {'vtitle': 'Hellò'},
            {'title': 'Hellò', 'yAxisSide': 'right'},
            {'uniqueLegend': 'true', '_expr': 'secondYAxis({0})'},
            {'uniqueLegend': 'true', 'vtitleRight': 'foo',
             '_expr': 'secondYAxis({0})'},
            {'graphOnly': 'true', 'yUnitSystem': 'si'},
            {'lineMode': 'staircase'},
            {'lineMode': 'slope'},
            {'lineMode': 'connected'},
            {'min': 1, 'max': 2, 'thickness': 2, 'yUnitSystem': 'none'},
            {'yMax': 5, 'yLimit': 0.5, 'yStep': 0.1},
            {'yMax': 'max', 'yUnitSystem': 'binary'},
            {'areaMode': 'stacked', '_expr': 'stacked({0})'},
            {'lineMode': 'staircase', '_expr': 'stacked({0})'},
            {'areaMode': 'first', '_expr': 'stacked({0})'},
            {'areaMode': 'all', '_expr': 'stacked({0})'},
            {'areaMode': 'stacked', 'areaAlpha': 0.5, '_expr': 'stacked({0})'},
            {'areaMode': 'stacked', 'areaAlpha': 'a', '_expr': 'stacked({0})'},
            {'_expr': 'dashed(lineWidth({0}, 5))'},
            {'target': 'areaBetween(*)'},
            {'drawNullAsZero': 'true'},
            {'_expr': 'drawAsInfinite({0})'},
            {'graphType': 'pie', 'pieMode': 'average', 'title': 'Pie'},
            {'graphType': 'pie', 'pieMode': 'average', 'hideLegend': 'true'},
            {'graphType': 'pie', 'pieMode': 'average', 'valueLabels': 'none'},
            {'graphType': 'pie', 'pieMode': 'average',
             'valueLabels': 'number'},
            {'graphType': 'pie', 'pieMode': 'average', 'pieLabels': 'rotated'},
            {'noCache': 'true'},
            {'cacheTimeout': 5},
            {'cacheTimeout': 5},  # cache hit
        ]:
            if qs.setdefault('target', ['foo', 'test']) == ['foo', 'test']:
                if '_expr' in qs:
                    expr = qs.pop('_expr')
                    qs['target'] = [expr.format(t) for t in qs['target']]
            response = self.app.get(self.url, query_string=qs)
            self.assertEqual(response.status_code, 200)
            self.assertEqual(response.headers['Content-Type'], 'image/png')
            if Cache is None or qs.get('noCache'):
                self.assertEqual(response.headers['Pragma'], 'no-cache')
                self.assertEqual(response.headers['Cache-Control'], 'no-cache')
                self.assertFalse('Expires' in response.headers)
            else:
                self.assertEqual(response.headers['Cache-Control'],
                                 'max-age={0}'.format(
                                     qs.get('cacheTimeout', 60)))
                self.assertNotEqual(response.headers['Cache-Control'],
                                    'no-cache')
                self.assertFalse('Pragma' in response.headers)

        for qs in [
            {'bgcolor': 'foo'},
        ]:
            qs['target'] = 'test'
            with self.assertRaises(ValueError):
                response = self.app.get(self.url, query_string=qs)

        for qs in [
            {'lineMode': 'stacked'},
        ]:
            qs['target'] = 'test'
            with self.assertRaises(AssertionError):
                response = self.app.get(self.url, query_string=qs)

    def test_render_validation(self):
        whisper.create(self.db, [(1, 60)])

        response = self.app.get(self.url)
        self.assertJSON(response, {'errors': {
            'target': 'This parameter is required.'}}, status_code=400)

        response = self.app.get(self.url, query_string={'graphType': 'foo',
                                                        'target': 'test'})
        self.assertJSON(response, {'errors': {
            'graphType': "Invalid graphType 'foo', must be one of 'line', "
            "'pie'."}}, status_code=400)

        response = self.app.get(self.url, query_string={'maxDataPoints': 'foo',
                                                        'target': 'test'})
        self.assertJSON(response, {'errors': {
            'maxDataPoints': 'Must be an integer.'}}, status_code=400)

        response = self.app.get(self.url, query_string={
            'from': '21:2020140313',
            'until': '21:2020140313',
            'target': 'test'})
        self.assertJSON(response, {'errors': {
            'from': 'Invalid empty time range',
            'until': 'Invalid empty time range',
        }}, status_code=400)

        response = self.app.get(self.url, query_string={
            'target': 'foo',
            'width': 100,
            'thickness': '1.5',
            'fontBold': 'true',
            'fontItalic': 'default',
        })
        self.assertEqual(response.status_code, 200)

        response = self.app.get(self.url, query_string={
            'target': 'foo', 'tz': 'Europe/Lausanne'})
        self.assertJSON(response, {'errors': {
            'tz': "Unknown timezone: 'Europe/Lausanne'.",
        }}, status_code=400)

        response = self.app.get(self.url, query_string={'target': 'test:aa',
                                                        'graphType': 'pie'})
        self.assertJSON(response, {'errors': {
            'target': "Invalid target: 'test:aa'.",
        }}, status_code=400)

        response = self.app.get(self.url, query_string={
            'target': ['test', 'foo:1.2'], 'graphType': 'pie'})
        self.assertEqual(response.status_code, 200)

        response = self.app.get(self.url, query_string={'target': ['test',
                                                                   '']})
        self.assertEqual(response.status_code, 200)

        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'format': 'csv'})
        lines = response.data.decode('utf-8').strip().split('\n')
        # 59 is a time race cond
        self.assertTrue(len(lines) in [59, 60])
        self.assertFalse(any([l.strip().split(',')[2] for l in lines]))

        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'format': 'svg',
                                                        'jsonp': 'foo'})
        jsonpsvg = response.data.decode('utf-8')
        self.assertTrue(jsonpsvg.startswith('foo("<?xml version=\\"1.0\\"'))
        self.assertTrue(jsonpsvg.endswith('</script>\\n</svg>")'))

        response = self.app.get(self.url, query_string={'target': 'test',
                                                        'format': 'svg'})
        svg = response.data.decode('utf-8')
        self.assertTrue(svg.startswith('<?xml version="1.0"'))

        response = self.app.get(self.url, query_string={'target': 'inexisting',
                                                        'format': 'svg'})
        self.assertEqual(response.status_code, 200)
        svg = response.data.decode('utf-8')
        self.assertTrue(svg.startswith('<?xml version="1.0"'))

        response = self.app.get(self.url, query_string={
            'target': 'sum(test)',
        })
        self.assertEqual(response.status_code, 200)

{'fraction_non_alphanumeric': 0.17547339386989805, 'fraction_numerical': 0.024421235317816963, 'mean_word_length': 1.749723050703025, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 6, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 6}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 86, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7421158', 'n_tokens_mistral': 9994, 'n_tokens_neox': 9376, 'n_words': 1816}