# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License.
# This product includes software developed at Datadog (https://www.datadoghq.com/).
# Copyright 2015-Present Datadog, Inc
"""
Tests for the ThreadStats class, using HTTP mode
"""

# stdlib
import logging
import os
import random
import time
import unittest

# 3p
from mock import patch

# datadog
from datadog import ThreadStats, lambda_metric, datadog_lambda_wrapper
from datadog.threadstats.aws_lambda import _get_lambda_stats
from tests.util.contextmanagers import preserve_environment_variable, EnvVars

# Silence the logger.
logger = logging.getLogger('dd.datadogpy')
logger.setLevel(logging.ERROR)


class MemoryReporter(object):
    """
    A reporting class that reports to memory for testing.
    """

    def __init__(self):
        self.distributions = []
        self.metrics = []
        self.events = []
        self.dist_flush_counter = 0

    def flush_distributions(self, distributions):
        self.distributions += distributions
        self.dist_flush_counter = self.dist_flush_counter + 1

    def flush_metrics(self, metrics):
        self.metrics += metrics

    def flush_events(self, events):
        self.events += events


class TestUnitThreadStats(unittest.TestCase):
    """
    Unit tests for ThreadStats.
    """
    def setUp(self):
        """
        Set a mocked reporter.
        """
        self.reporter = MemoryReporter()

    def sort_metrics(self, metrics):
        """
        Sort metrics by timestamp of first point and then name.
        """
        def sort(metric):
            tags = metric['tags'] or []
            host = metric['host'] or ''
            return (metric['points'][0][0], metric['metric'], tags, host,
                    metric['points'][0][1])
        return sorted(metrics, key=sort)

    def assertMetric(self, name=None, value=None, tags=None, count=None):
        """
        Helper, to make assertions on metrics.
        """
        matching_metrics = []

        for metric in self.reporter.metrics:
            if name and name != metric['metric']:
                continue
            if value and value != metric['points'][0][1]:
                continue
            if tags and tags != metric['tags']:
                continue
            matching_metrics.append(metric)

        if count:
            self.assertEqual(
                len(matching_metrics), count,
                u"Candidate size assertion failure: expected {expected}, found {count}. "
                u"Metric name={name}, value={value}, tags={tags}.".format(
                    expected=count, count=len(matching_metrics),
                    name=name, value=value, tags=tags
                )
            )
        else:
            self.assertTrue(
                len(matching_metrics) > 0,
                u"Candidate size assertion failure: no matching metric found. "
                u"Metric name={name}, value={value}, tags={tags}.".format(
                    name=name, value=value, tags=tags
                )
            )

    def test_init(self):
        # Test compress_payload setting
        t = ThreadStats(compress_payload=True)
        t.start()
        assert t.reporter.compress_payload is True
        t.stop()
        # Default value
        t = ThreadStats()
        t.start()
        assert t.reporter.compress_payload is False
        t.stop()

    def test_timed_decorator(self):
        dog = ThreadStats()
        dog.start(roll_up_interval=1, flush_in_thread=False)
        reporter = dog.reporter = MemoryReporter()

        @dog.timed('timed.test')
        def func(a, b, c=1, d=1):
            """docstring"""
            return (a, b, c, d)

        assert func.__name__ == 'func'
        assert func.__doc__ == 'docstring'

        result = func(1, 2, d=3)
        # Assert it handles args and kwargs correctly.
        assert result == (1, 2, 1, 3)
        time.sleep(1)  # Argh. I hate this.
        dog.flush()
        metrics = self.sort_metrics(reporter.metrics)
        assert len(metrics) == 8
        (_, _, _, _, avg, count, max_, min_) = metrics
        assert avg['metric'] == 'timed.test.avg'
        assert count['metric'] == 'timed.test.count'
        assert max_['metric'] == 'timed.test.max'
        assert min_['metric'] == 'timed.test.min'

    def test_event(self):
        dog = ThreadStats()
        dog.start(roll_up_interval=10, flush_in_thread=False)
        reporter = dog.reporter = MemoryReporter()

        # Add two events
        event1_title = "Event 1 title"
        event2_title = "Event 1 title"
        event1_text = "Event 1 text"
        event2_text = "Event 2 text"
        dog.event(event1_title, event1_text)
        dog.event(event2_title, event2_text)

        # Flush and test
        dog.flush()
        event1, event2 = reporter.events
        assert event1['title'] == event1_title
        assert event1['text'] == event1_text
        assert event2['title'] == event2_title
        assert event2['text'] == event2_text

        # Test more parameters
        reporter.events = []
        event1_priority = "low"
        event1_date_happened = 1375296969
        event1_tag = "Event 2 tag"
        dog.event(event1_title, event1_text, priority=event1_priority,
                  date_happened=event1_date_happened, tags=[event1_tag])

        # Flush and test
        dog.flush()
        event, = reporter.events
        assert event['title'] == event1_title
        assert event['text'] == event1_text
        assert event['priority'] == event1_priority
        assert event['date_happened'] == event1_date_happened
        assert event['tags'] == [event1_tag]

    def test_event_constant_tags(self):
        constant_tag = 'type:constant'
        dog = ThreadStats(constant_tags=[constant_tag])
        dog.start(roll_up_interval=10, flush_in_thread=False)
        reporter = dog.reporter = MemoryReporter()

        # Add two events
        event1_title = "Event 1 title"
        event2_title = "Event 1 title"
        event1_text = "Event 1 text"
        event2_text = "Event 2 text"
        dog.event(event1_title, event1_text)
        dog.event(event2_title, event2_text)

        # Flush and test
        dog.flush()
        event1, event2 = reporter.events
        assert event1['title'] == event1_title
        assert event1['text'] == event1_text
        assert event1['tags'] == [constant_tag]
        assert event2['title'] == event2_title
        assert event2['text'] == event2_text
        assert event2['text'] == event2_text
        assert event2['tags'] == [constant_tag]

        # Test more parameters
        reporter.events = []
        event1_priority = "low"
        event1_date_happened = 1375296969
        event1_tag = "Event 2 tag"
        dog.event(event1_title, event1_text, priority=event1_priority,
                  date_happened=event1_date_happened, tags=[event1_tag])

        # Flush and test
        dog.flush()
        event, = reporter.events
        assert event['title'] == event1_title
        assert event['text'] == event1_text
        assert event['priority'] == event1_priority
        assert event['date_happened'] == event1_date_happened
        assert event['tags'] == [event1_tag, constant_tag]

    def test_histogram(self):
        dog = ThreadStats()
        dog.start(roll_up_interval=10, flush_in_thread=False)
        reporter = dog.reporter = MemoryReporter()

        # Add some histogram metrics.
        dog.histogram('histogram.1', 20, 100.0)
        dog.histogram('histogram.1', 30, 105.0)
        dog.histogram('histogram.1', 40, 106.0)
        dog.histogram('histogram.1', 50, 106.0)

        dog.histogram('histogram.1', 30, 110.0)
        dog.histogram('histogram.1', 50, 115.0)
        dog.histogram('histogram.1', 40, 116.0)

        dog.histogram('histogram.2', 40, 100.0)

        dog.histogram('histogram.3', 50, 134.0)

        # Flush and ensure they roll up properly.
        dog.flush(120.0)
        metrics = self.sort_metrics(reporter.metrics)
        assert len(metrics) == 24

        # Test histograms elsewhere.
        (h1751, h1851, h1951, h1991, h1avg1, h1cnt1, h1max1, h1min1,
         _, _, _, _, h2avg1, h2cnt1, h2max1, h2min1,
         h1752, _, _, h1992, h1avg2, h1cnt2, h1max2, h1min2) = metrics

        assert h1avg1['metric'] == 'histogram.1.avg'
        assert h1avg1['points'][0][0] == 100.0
        assert h1avg1['points'][0][1] == 35
        assert h1cnt1['metric'] == 'histogram.1.count'
        assert h1cnt1['points'][0][0] == 100.0
        assert h1cnt1['points'][0][1] == 0.4
        assert h1min1['metric'] == 'histogram.1.min'
        assert h1min1['points'][0][1] == 20
        assert h1max1['metric'] == 'histogram.1.max'
        assert h1max1['points'][0][1] == 50
        assert h1751['metric'] == 'histogram.1.75percentile'
        assert h1751['points'][0][1] == 40
        assert h1991['metric'] == 'histogram.1.99percentile'
        assert h1991['points'][0][1] == 50

        assert h1avg2['metric'] == 'histogram.1.avg'
        assert h1avg2['points'][0][0] == 110.0
        assert h1avg2['points'][0][1] == 40
        assert h1cnt2['metric'] == 'histogram.1.count'
        assert h1cnt2['points'][0][0] == 110.0
        assert h1cnt2['points'][0][1] == 0.3
        assert h1752['metric'] == 'histogram.1.75percentile'
        assert h1752['points'][0][0] == 110.0
        assert h1752['points'][0][1] == 40.0
        assert h1992['metric'] == 'histogram.1.99percentile'
        assert h1992['points'][0][0] == 110.0
        assert h1992['points'][0][1] == 50.0

        assert h2avg1['metric'] == 'histogram.2.avg'
        assert h2avg1['points'][0][0] == 100.0
        assert h2avg1['points'][0][1] == 40
        assert h2cnt1['metric'] == 'histogram.2.count'
        assert h2cnt1['points'][0][0] == 100.0
        assert h2cnt1['points'][0][1] == 0.1

        # Flush again ensure they're gone.
        dog.reporter.metrics = []
        dog.flush(140.0)
        assert len(dog.reporter.metrics) == 8
        dog.reporter.metrics = []
        dog.flush(200.0)
        assert len(dog.reporter.metrics) == 0

    def test_histogram_percentiles(self):
        dog = ThreadStats()
        dog.start(roll_up_interval=10, flush_in_thread=False)
        reporter = dog.reporter = MemoryReporter()
        # Sample all numbers between 1-100 many times. This
        # means our percentiles should be relatively close to themselves.
        percentiles = list(range(100))
        random.shuffle(percentiles)  # in place
        for i in percentiles:
            for j in range(20):
                dog.histogram('percentiles', i, 1000.0)
        dog.flush(2000.0)
        metrics = reporter.metrics

        def assert_almost_equal(i, j, e=1):
            # Floating point math?
            assert abs(i - j) <= e, "%s %s %s" % (i, j, e)
        assert len(metrics) == 8
        p75, p85, p95, p99, _, _, _, _ = self.sort_metrics(metrics)
        assert p75['metric'] == 'percentiles.75percentile'
        assert p75['points'][0][0] == 1000.0
        assert_almost_equal(p75['points'][0][1], 75, 8)
        assert_almost_equal(p85['points'][0][1], 85, 8)
        assert_almost_equal(p95['points'][0][1], 95, 8)
        assert_almost_equal(p99['points'][0][1], 99, 8)

    def test_gauge(self):
        # Create some fake metrics.
        dog = ThreadStats()
        dog.start(roll_up_interval=10, flush_in_thread=False)
        reporter = dog.reporter = MemoryReporter()

        dog.gauge('test.gauge.1', 20, 100.0)
        dog.gauge('test.gauge.1', 22, 105.0)
        dog.gauge('test.gauge.2', 30, 115.0)
        dog.gauge('test.gauge.3', 30, 125.0)
        dog.flush(120.0)

        # Assert they've been properly flushed.
        metrics = self.sort_metrics(reporter.metrics)
        assert len(metrics) == 2

        (first, second) = metrics
        assert first['metric'] == 'test.gauge.1'
        assert first['points'][0][0] == 100.0
        assert first['points'][0][1] == 22
        assert second['metric'] == 'test.gauge.2'

        # Flush again and make sure we're progressing.
        reporter.metrics = []
        dog.flush(130.0)
        assert len(reporter.metrics) == 1

        # Finally, make sure we've flushed all metrics.
        reporter.metrics = []
        dog.flush(150.0)
        assert len(reporter.metrics) == 0

    def test_set(self):
        # Create some fake metrics.
        dog = ThreadStats()
        dog.start(roll_up_interval=10, flush_in_thread=False)
        reporter = dog.reporter = MemoryReporter()

        dog.set('test.set.1', "a string", 100.0)
        dog.set('test.set.1', frozenset(), 105.0)
        dog.set('test.set.2', 30, 115.0)
        dog.set('test.set.3', 30, 125.0)
        dog.flush(120.0)

        # Assert they've been properly flushed.
        metrics = self.sort_metrics(reporter.metrics)
        assert len(metrics) == 2

        (first, second) = metrics
        assert first['metric'] == 'test.set.1'
        assert first['points'][0][0] == 100.0
        assert first['points'][0][1] == 2
        assert second['metric'] == 'test.set.2'
        assert second['points'][0][0] == 110.0
        assert second['points'][0][1] == 1

        # Flush again and make sure we're progressing.
        reporter.metrics = []
        dog.flush(130.0)
        metrics = self.sort_metrics(reporter.metrics)
        assert len(metrics) == 1
        assert metrics[0]['metric'] == 'test.set.3'
        assert metrics[0]['points'][0][0] == 120.0
        assert metrics[0]['points'][0][1] == 1

        # Finally, make sure we've flushed all metrics.
        reporter.metrics = []
        dog.flush(150.0)
        assert len(reporter.metrics) == 0

    def test_counter(self):
        # Create some fake metrics.
        dog = ThreadStats()
        dog.start(roll_up_interval=10, flush_in_thread=False)
        reporter = dog.reporter = MemoryReporter()

        dog.increment('test.counter.1', timestamp=1000.0)
        dog.increment('test.counter.1', value=2, timestamp=1005.0)
        dog.increment('test.counter.2', timestamp=1015.0)
        dog.increment('test.counter.3', timestamp=1025.0)
        dog.flush(1021.0)

        # Assert they've been properly flushed.
        metrics = self.sort_metrics(reporter.metrics)
        assert len(metrics) == 2
        (first, second) = metrics
        assert first['metric'] == 'test.counter.1'
        assert first['points'][0][0] == 1000.0
        assert first['points'][0][1] == 0.3
        assert second['metric'] == 'test.counter.2'

        # Test decrement
        dog.increment('test.counter.1', value=10, timestamp=1000.0)
        dog.decrement('test.counter.1', value=2, timestamp=1005.0)
        reporter.metrics = []
        dog.flush(1021.0)

        metrics = self.sort_metrics(reporter.metrics)
        assert len(metrics) == 1
        first, = metrics
        assert first['metric'] == 'test.counter.1'
        assert first['points'][0][0] == 1000.0
        assert first['points'][0][1] == 0.8
        assert second['metric'] == 'test.counter.2'

        # Flush again and make sure we're progressing.
        reporter.metrics = []
        dog.flush(1030.0)
        assert len(reporter.metrics) == 1

        # Finally, make sure we've flushed all metrics.
        reporter.metrics = []
        dog.flush(1050.0)
        assert len(reporter.metrics) == 0

    def test_distribution(self):
        # Create some fake metrics.
        dog = ThreadStats()
        dog.start(roll_up_interval=10, flush_in_thread=False)
        reporter = dog.reporter = MemoryReporter()

        dog.distribution('test.dist.1', 20, 100.0)
        dog.distribution('test.dist.1', 22, 105.0)
        dog.distribution('test.dist.2', 30, 115.0)
        dog.distribution('test.dist.3', 30, 125.0)
        dog.flush(120.0)

        # Assert they've been properly flushed.
        dists = self.sort_metrics(reporter.distributions)
        assert len(dists) == 2

        (first, second) = dists
        assert first['metric'] == 'test.dist.1'
        assert first['points'][0][0] == 100.0
        assert first['points'][0][1] == [20, 22]
        assert second['metric'] == 'test.dist.2'

        # Flush again and make sure we're progressing.
        reporter.distributions = []
        dog.flush(130.0)
        assert len(reporter.distributions) == 1

        # Finally, make sure we've flushed all metrics.
        reporter.distributions = []
        dog.flush(150.0)
        assert len(reporter.distributions) == 0

    def test_default_host_and_device(self):
        dog = ThreadStats()
        dog.start(roll_up_interval=1, flush_in_thread=False)
        reporter = dog.reporter = MemoryReporter()
        dog.gauge('my.gauge', 1, 100.0)
        dog.flush(1000)
        metric = reporter.metrics[0]
        assert not metric['device']
        assert not metric['host']

    def test_custom_host_and_device(self):
        dog = ThreadStats()
        dog.start(roll_up_interval=1, flush_in_thread=False, device='dev')
        reporter = dog.reporter = MemoryReporter()
        dog.gauge('my.gauge', 1, 100.0, host='host')
        dog.flush(1000)
        metric = reporter.metrics[0]
        assert metric['device'] == 'dev'
        assert metric['host'] == 'host'

    def test_tags(self):
        dog = ThreadStats()
        dog.start(roll_up_interval=10, flush_in_thread=False)
        reporter = dog.reporter = MemoryReporter()

        # Post the same metric with different tags.
        dog.gauge('gauge', 10, timestamp=100.0)
        dog.gauge('gauge', 15, timestamp=100.0, tags=['env:production', 'db'])
        dog.gauge('gauge', 20, timestamp=100.0, tags=['env:staging'])

        dog.increment('counter', timestamp=100.0)
        dog.increment('counter', timestamp=100.0, tags=['env:production', 'db'])
        dog.increment('counter', timestamp=100.0, tags=['env:staging'])

        dog.flush(200.0)

        metrics = self.sort_metrics(reporter.metrics)
        assert len(metrics) == 6

        [c1, c2, c3, g1, g2, g3] = metrics
        assert c1['metric'] == 'counter'
        assert c2['metric'] == 'counter'
        assert c3['metric'] == 'counter'
        assert c1['tags'] is None
        assert c1['points'][0][1] == 0.1
        assert c2['tags'] == ['env:production', 'db']
        assert c2['points'][0][1] == 0.1
        assert c3['tags'] == ['env:staging']
        assert c3['points'][0][1] == 0.1

        assert g1['metric'] == 'gauge'
        assert g2['metric'] == 'gauge'
        assert g3['metric'] == 'gauge'
        assert g1['tags'] is None
        assert g1['points'][0][1] == 10
        assert g2['tags'] == ['env:production', 'db']
        assert g2['points'][0][1] == 15
        assert g3['tags'] == ['env:staging']
        assert g3['points'][0][1] == 20

    def test_constant_tags(self):
        """
        Constant tags are attached to all metrics.
        """
        dog = ThreadStats(constant_tags=["type:constant"])
        dog.start(roll_up_interval=1, flush_in_thread=False)
        dog.reporter = self.reporter

        # Post the same metric with different tags.
        dog.gauge("gauge", 10, timestamp=100.0)
        dog.gauge("gauge", 15, timestamp=100.0, tags=["env:production", 'db'])
        dog.gauge("gauge", 20, timestamp=100.0, tags=["env:staging"])

        dog.increment("counter", timestamp=100.0)
        dog.increment("counter", timestamp=100.0, tags=["env:production", 'db'])
        dog.increment("counter", timestamp=100.0, tags=["env:staging"])

        dog.flush(200.0)

        # Assertions on all metrics
        self.assertMetric(count=6)

        # Assertions on gauges
        self.assertMetric(name='gauge', value=10, tags=["type:constant"], count=1)
        self.assertMetric(name="gauge", value=15,
                          tags=["env:production", "db", "type:constant"], count=1)  # noqa
        self.assertMetric(name="gauge", value=20, tags=["env:staging", "type:constant"], count=1)

        # Assertions on counters
        self.assertMetric(name="counter", value=1, tags=["type:constant"], count=1)
        self.assertMetric(name="counter", value=1,
                          tags=["env:production", "db", "type:constant"], count=1)  # noqa
        self.assertMetric(name="counter", value=1, tags=["env:staging", "type:constant"], count=1)

        # Ensure histograms work as well.
        @dog.timed('timed', tags=['version:1'])
        def do_nothing():
            """
            A function that does nothing, but being timed.
            """
            pass

        with patch("datadog.threadstats.base.time", return_value=300):
            do_nothing()

        dog.histogram('timed', 20, timestamp=300.0, tags=['db', 'version:2'])

        self.reporter.metrics = []
        dog.flush(400.0)

        # Histograms, and related metric types, produce 8 different metrics
        self.assertMetric(tags=["version:1", "type:constant"], count=8)
        self.assertMetric(tags=["db", "version:2", "type:constant"], count=8)

    def test_metric_namespace(self):
        """
        Namespace prefixes all metric names.
        """
        # Set up ThreadStats with a namespace
        dog = ThreadStats(namespace="foo")
        dog.start(roll_up_interval=1, flush_in_thread=False)
        dog.reporter = self.reporter

        # Send a few metrics
        dog.gauge("gauge", 20, timestamp=100.0)
        dog.increment("counter", timestamp=100.0)
        dog.flush(200.0)

        # Metric names are prefixed with the namespace
        self.assertMetric(count=2)
        self.assertMetric(name="foo.gauge", count=1)
        self.assertMetric(name="foo.counter", count=1)

    def test_host(self):
        dog = ThreadStats()
        dog.start(roll_up_interval=10, flush_in_thread=False)
        reporter = dog.reporter = MemoryReporter()

        # Post the same metric with different tags.
        dog.gauge('gauge', 12, timestamp=100.0, host='')  # unset the host
        dog.gauge('gauge', 10, timestamp=100.0)
        dog.gauge('gauge', 15, timestamp=100.0, host='test')
        dog.gauge('gauge', 15, timestamp=100.0, host='test')

        dog.increment('counter', timestamp=100.0)
        dog.increment('counter', timestamp=100.0)
        dog.increment('counter', timestamp=100.0, host='test')
        dog.increment('counter', timestamp=100.0, host='test', tags=['tag'])
        dog.increment('counter', timestamp=100.0, host='test', tags=['tag'])

        dog.flush(200.0)

        metrics = self.sort_metrics(reporter.metrics)
        assert len(metrics) == 6

        [c1, c2, c3, g1, g2, g3] = metrics
        assert c1['metric'] == 'counter'
        assert c2['metric'] == 'counter'
        assert c3['metric'] == 'counter'
        assert c1['host'] is None
        assert c1['tags'] is None
        assert c1['points'][0][1] == 0.2
        assert c2['host'] == 'test'
        assert c2['tags'] is None
        assert c2['points'][0][1] == 0.1
        assert c3['host'] == 'test'
        assert c3['tags'] == ['tag']
        assert c3['points'][0][1] == 0.2

        assert g1['metric'] == 'gauge'
        assert g2['metric'] == 'gauge'
        assert g3['metric'] == 'gauge'
        assert g1['host'] is None
        assert g1['points'][0][1] == 10
        assert g2['host'] == ''
        assert g2['points'][0][1] == 12
        assert g3['host'] == 'test'
        assert g3['points'][0][1] == 15

        # Ensure histograms work as well.
        @dog.timed('timed', host='test')
        def test():
            pass
        test()
        dog.histogram('timed', 20, timestamp=300.0, host='test')
        reporter.metrics = []
        dog.flush(400)
        for metric in reporter.metrics:
            assert metric['host'] == 'test'

    def test_disabled_mode(self):
        dog = ThreadStats()
        dog.start(disabled=True, flush_interval=1, roll_up_interval=1)
        reporter = dog.reporter = MemoryReporter()
        dog.gauge('testing', 1, timestamp=1000)
        dog.gauge('testing', 2, timestamp=1000)
        dog.flush(2000.0)
        assert not reporter.metrics

    def test_stop(self):
        dog = ThreadStats()
        dog.start(flush_interval=1, roll_up_interval=1)
        dog.reporter = MemoryReporter()
        for i in range(10):
            dog.gauge('metric', i)
        time.sleep(2)
        flush_count = dog.flush_count
        assert flush_count
        dog.stop()
        for i in range(10):
            dog.gauge('metric', i)
        time.sleep(2)
        for i in range(10):
            dog.gauge('metric', i)
        time.sleep(2)
        assert dog.flush_count in [flush_count, flush_count + 1]

    def test_tags_from_environment(self):
        test_tags = ['country:china', 'age:45', 'blue']
        with preserve_environment_variable('DATADOG_TAGS'):
            os.environ['DATADOG_TAGS'] = ','.join(test_tags)
            dog = ThreadStats()
        dog.start(roll_up_interval=10, flush_in_thread=False)
        reporter = dog.reporter = MemoryReporter()

        # Add two events
        event1_title = "Event 1 title"
        event2_title = "Event 1 title"
        event1_text = "Event 1 text"
        event2_text = "Event 2 text"
        dog.event(event1_title, event1_text)
        dog.event(event2_title, event2_text)

        # Flush and test
        dog.flush()
        event1, event2 = reporter.events
        assert event1['title'] == event1_title
        assert event1['text'] == event1_text
        assert event1['tags'] == test_tags
        assert event2['title'] == event2_title
        assert event2['text'] == event2_text
        assert event2['text'] == event2_text
        assert event2['tags'] == test_tags

        # Test more parameters
        reporter.events = []
        event1_priority = "low"
        event1_date_happened = 1375296969
        event1_tag = "Event 2 tag"
        dog.event(event1_title, event1_text, priority=event1_priority,
                  date_happened=event1_date_happened, tags=[event1_tag])

        # Flush and test
        dog.flush()
        event, = reporter.events
        assert event['title'] == event1_title
        assert event['text'] == event1_text
        assert event['priority'] == event1_priority
        assert event['date_happened'] == event1_date_happened
        assert event['tags'] == [event1_tag] + test_tags
        dog.start(flush_interval=1, roll_up_interval=1)

    def test_tags_from_environment_and_constant(self):
        test_tags = ['country:china', 'age:45', 'blue']
        constant_tags = ['country:canada', 'red']
        with preserve_environment_variable('DATADOG_TAGS'):
            os.environ['DATADOG_TAGS'] = ','.join(test_tags)
            dog = ThreadStats(constant_tags=constant_tags)
        dog.start(roll_up_interval=10, flush_in_thread=False)
        reporter = dog.reporter = MemoryReporter()

        # Add two events
        event1_title = "Event 1 title"
        event2_title = "Event 1 title"
        event1_text = "Event 1 text"
        event2_text = "Event 2 text"
        dog.event(event1_title, event1_text)
        dog.event(event2_title, event2_text)

        # Flush and test
        dog.flush()
        event1, event2 = reporter.events
        assert event1['title'] == event1_title
        assert event1['text'] == event1_text
        assert event1['tags'] == constant_tags + test_tags
        assert event2['title'] == event2_title
        assert event2['text'] == event2_text
        assert event2['text'] == event2_text
        assert event2['tags'] == constant_tags + test_tags

        # Test more parameters
        reporter.events = []
        event1_priority = "low"
        event1_date_happened = 1375296969
        event1_tag = "Event 2 tag"
        dog.event(event1_title, event1_text, priority=event1_priority,
                  date_happened=event1_date_happened, tags=[event1_tag])

        # Flush and test
        dog.flush()
        event, = reporter.events
        assert event['title'] == event1_title
        assert event['text'] == event1_text
        assert event['priority'] == event1_priority
        assert event['date_happened'] == event1_date_happened
        assert event['tags'] == [event1_tag] + constant_tags + test_tags
        dog.start(flush_interval=1, roll_up_interval=1)

    def test_tags_from_environment_env_service_version(self):
        test_tags = set(['env:staging', 'service:food', 'version:1.2.3'])
        with EnvVars(
            env_vars={
                "DD_ENV": "staging",
                "DD_VERSION": "1.2.3",
                "DD_SERVICE": "food",
            }
        ):
            dog = ThreadStats()
        dog.start(roll_up_interval=10, flush_in_thread=False)
        reporter = dog.reporter = MemoryReporter()

        # Add two events
        event1_title = "Event 1 title"
        event1_text = "Event 1 text"
        dog.event(event1_title, event1_text)

        # Flush and test
        dog.flush()
        [event1] = reporter.events
        assert event1['title'] == event1_title
        assert event1['text'] == event1_text
        assert set(event1['tags']) == test_tags

    def test_metric_type(self):
        """
        Checks the submitted metric's metric type.
        """
        # Set up ThreadStats with a namespace
        dog = ThreadStats(namespace="foo")
        dog.start(roll_up_interval=1, flush_in_thread=False)
        reporter = dog.reporter = self.reporter

        # Send a few metrics
        dog.gauge("gauge", 20, timestamp=100.0)
        dog.increment("counter", timestamp=100.0)
        dog.histogram('histogram.1', 20, 100.0)
        dog.flush(200.0)

        (first, second, p75, p85, p95, p99, avg, cnt, max_, min_) = self.sort_metrics(reporter.metrics)

        # Assert Metric type
        assert first['type'] == 'rate'
        assert second['type'] == 'gauge'
        assert p75['type'] == 'gauge'
        assert p85['type'] == 'gauge'
        assert p95['type'] == 'gauge'
        assert p99['type'] == 'gauge'
        assert avg['type'] == 'gauge'
        assert cnt['type'] == 'rate'
        assert max_['type'] == 'gauge'
        assert min_['type'] == 'gauge'

    # Test lambda_wrapper (uses ThreadStats under the hood)
    def test_basic_lambda_decorator(self):

        @datadog_lambda_wrapper
        def basic_wrapped_function():
            lambda_metric("lambda.somemetric", 100)

        _get_lambda_stats().reporter = self.reporter
        basic_wrapped_function()

        assert _get_lambda_stats().reporter.dist_flush_counter == 1
        dists = self.sort_metrics(_get_lambda_stats().reporter.distributions)
        assert len(dists) == 1

    def test_embedded_lambda_decorator(self):
        """
        Test that the lambda decorator flushes metrics correctly and only once
        """

        @datadog_lambda_wrapper
        def wrapped_function_1():
            lambda_metric("lambda.dist.1", 10)

        @datadog_lambda_wrapper
        def wrapped_function_2():
            wrapped_function_1()
            lambda_metric("lambda.dist.2", 30)

        _get_lambda_stats().reporter = self.reporter
        wrapped_function_2()
        assert _get_lambda_stats().reporter.dist_flush_counter == 1

        dists = self.sort_metrics(_get_lambda_stats().reporter.distributions)
        assert len(dists) == 2
