experiment.py
author Vladan Djeric <vdjeric@mozilla.com>
Thu, 22 Oct 2015 17:42:30 -0400
changeset 56 dfb488cbd0dee6cf6c95fe76bb91deeca22dcec7
parent 46 d2683a9117496e57bdbdb26f75746846c1b19105
child 74 cefdc8498df33807be44e22aa0d45379e994bf7c
permissions -rw-r--r--
Fixed tag release_tag, associated it with changeset 0032fbee999a

import json
import datetime
import re
import os

earliestTime = datetime.datetime(2014, 1, 1)
latestTime = datetime.datetime(2100, 1, 1)

SECONDS_PER_DAY = 60 * 60 * 24

BUILDID_PATTERN = re.compile(r'\d{8,14}$')

def validate_time(d):
    validate_int(d)
    dt = datetime.datetime.utcfromtimestamp(d)
    if dt < earliestTime or dt > latestTime:
        raise ValueError("Timestamp is out of bounds: %s" % (d,))

def validate_int(d):
    if not isinstance(d, (int, long)):
        raise ValueError("Value %s is not an integer" % (d,))

def validate_max_seconds(d):
    validate_int(d)
    if d < SECONDS_PER_DAY or d > SECONDS_PER_DAY * 180:
        raise ValueError("secondsPerDay should be between one and 180 days")

def validate_app_name(d):
    if not isinstance(d, list) or len(d) != 1 or d[0] != "Firefox":
        raise ValueError('appName must be ["Firefox"]')

def validate_string_list(d):
    if not isinstance(d, list) or not len(d):
        raise ValueError("expected list of at least one string, got %s" % (d,))
    for e in d:
        validate_string(e)

def validate_string(d):
    if not isinstance(d, (str, unicode)):
        raise ValueError("Expected a string, got %s" % (d,))

def validate_buildid(d):
    if not BUILDID_PATTERN.match(d):
        raise ValueError("Expected a buildid, got %s" % (d,))

def validate_buildids(d):
    validate_string_list(d)
    for e in d:
        validate_buildid(e)

def validate_sample(d):
    if d < 0 or d > 1:
        raise ValueError("Sample must be between 0 and 1, got %s" % (d,))

def validate_bool(d):
    if not isinstance(d, bool):
        raise ValueError("Expected boolean type, got %s" % (d,))

required = [
    ("id", validate_string),
    ("startTime", validate_time),
    ("endTime", validate_time),
    ("maxActiveSeconds", validate_max_seconds),
    ("appName", validate_app_name),
    ("channel", validate_string_list),
]

optional = [
    ("maxStartTime", validate_time),
    ("minVersion", validate_string),
    ("maxVersion", validate_string),
    ("version", validate_string_list),
    ("minBuildID", validate_buildid),
    ("maxBuildID", validate_buildid),
    ("buildIDs", validate_buildids),
    ("os", validate_string_list),
    ("locale", validate_string_list),
    ("sample", validate_sample),
    ("disabled", validate_bool),
    ("frozen", validate_bool),
]

meta = [
    "publish",
    "priority",
    "name",
    "description",
    "info",
    "manifest",
]

class Experiment(object):
    def __init__(self, path):
        self.path = path
        d = json.load(open(os.path.join(path, "manifest.json")))

        keys = set(d.keys())
        for k in meta:
            keys.remove(k)
            setattr(self, k, d[k])
        if len(keys):
            raise ValueError("Unexpected toplevel keys: %s" % (".".join(keys),))

        keys = set(self.manifest.keys())
        for k, validate in required:
            keys.remove(k)
            validate(self.manifest[k])

        for k, validate in optional:
            if k in keys:
                validate(self.manifest[k])
                keys.remove(k)

        if len(keys):
            raise ValueError("Unexpected manifest keys: %s" % (",".join(keys),))

        self.disabled = self.manifest.get("disabled", False)

        endTime = datetime.datetime.utcfromtimestamp(self.manifest["endTime"])
        if endTime < datetime.datetime.utcnow():
            self.finished = True
        else:
            self.finished = False

        filterfile = os.path.join(path, "filter.js")
        if os.path.exists(filterfile):
            self.manifest["jsfilter"] = open(filterfile).read().encode("utf-8")