File: audit_runtime_enabled_features.py

package info (click to toggle)
chromium-browser 41.0.2272.118-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie-kfreebsd
  • size: 2,189,132 kB
  • sloc: cpp: 9,691,462; ansic: 3,341,451; python: 712,689; asm: 518,779; xml: 208,926; java: 169,820; sh: 119,353; perl: 68,907; makefile: 28,311; yacc: 13,305; objc: 11,385; tcl: 3,186; cs: 2,225; sql: 2,217; lex: 2,215; lisp: 1,349; pascal: 1,256; awk: 407; ruby: 155; sed: 53; php: 14; exp: 11
file content (176 lines) | stat: -rwxr-xr-x 5,912 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#!/usr/bin/env python

import requests
import re
from in_file import InFile

BRANCH_FORMAT = "https://src.chromium.org/blink/branches/chromium/%s/%s"
TRUNK_PATH = "Source/platform/RuntimeEnabledFeatures.in"
TRUNK_URL = "https://src.chromium.org/blink/trunk/%s" % TRUNK_PATH


def features_path(branch):
    # RuntimeEnabledFeatures has only existed since April 2013:
    if branch <= 1453:
        return None
    # Source/core/page/RuntimeEnabledFeatures.in existed by 1547
    # but was in an old format without status= arguments.
    if branch <= 1547:
        return None
    if branch <= 1650:
        return "Source/core/page/RuntimeEnabledFeatures.in"
    # Modern location:
    return TRUNK_PATH


def parse_features_file(features_text):
    valid_values = {
        'status': ['stable', 'experimental', 'deprecated', 'test'],
    }
    defaults = {
        'condition': None,
        'depends_on': [],
        'custom': False,
        'status': None,
    }

    # FIXME: in_file.py manually calls str.strip so conver to str here.
    features_lines = str(features_text).split("\n")
    return InFile(features_lines, defaults, valid_values)


def stable_features(in_file):
    return [feature['name'] for feature in in_file.name_dictionaries if feature['status'] == 'stable']


def branch_from_version(version_string):
    # Format: 31.0.1650.63, the second digit was only ever used for M4
    # no clue what it's actually intended for.
    version_regexp = r"(?P<major>\d+)\.\d+\.(?P<branch>\d+)\.(?P<minor>\d+)"
    match = re.match(version_regexp, version_string)
    # if match == None, we'll blow up, so at least provide some debugging information:
    if not match:
        print version_string
    return int(match.group('branch'))


def print_feature_diff(added_features, removed_features):
    for feature in added_features:
        print "+ %s" % feature
    for feature in removed_features:
        print "- %s" % feature


def historical_versions(os_string, channel):
    url_pattern = "http://omahaproxy.appspot.com/history?os=%s&channel=%s"
    url = url_pattern % (os_string, channel)
    releases_csv = requests.get(url).text.strip("\n")
    # Format: os,channel,version_string,date_string
    lines = releases_csv.split('\n')
    # As of June 2014, omahaproxy is now including headers:
    assert(lines[0] == 'os,channel,version,timestamp')
    # FIXME: We could replace this with more generic CSV parsing now that we have headers.
    return [line.split(',')[2] for line in lines[1:]]


def feature_file_url_for_branch(branch):
    path = features_path(branch)
    if not path:
        return None
    return BRANCH_FORMAT % (branch, path)


def feature_file_for_branch(branch):
    url = feature_file_url_for_branch(branch)
    if not url:
        return None
    return parse_features_file(requests.get(url).text)


def historical_feature_tuples(os_string, channel):
    feature_tuples = []
    version_strings = reversed(historical_versions(os_string, channel))
    seen_branches = set()

    for version in version_strings:
        branch = branch_from_version(version)
        if branch in seen_branches:
            continue
        seen_branches.add(branch)

        feature_file = feature_file_for_branch(branch)
        if not feature_file:
            continue
        feature_tuple = (version, feature_file)
        feature_tuples.append(feature_tuple)
    return feature_tuples


class FeatureAuditor(object):
    def __init__(self):
        self.last_features = []

    def add_version(self, version_name, feature_file):
        features = stable_features(feature_file)
        if self.last_features:
            added_features = list(set(features) - set(self.last_features))
            removed_features = list(set(self.last_features) - set(features))

            print "\n%s:" % version_name
            print_feature_diff(added_features, removed_features)

        self.last_features = features


def active_feature_tuples(os_string):
    feature_tuples = []
    current_releases_url = "http://omahaproxy.appspot.com/all.json"
    trains = requests.get(current_releases_url).json()
    train = next(train for train in trains if train['os'] == os_string)
    # FIXME: This is depending on the ordering of the json, we could
    # use use sorted() with true_branch, but that would put None first.
    for version in reversed(train['versions']):
        # FIXME: This is lame to exclude stable, the caller should
        # ignore it if it doesn't want it.
        if version['channel'] == 'stable':
            continue  # handled by historical_feature_tuples
        branch = version['true_branch']
        if branch:
            feature_file = feature_file_for_branch(branch)
        else:
            feature_file = parse_features_file(requests.get(TRUNK_URL).text)

        name = "%(version)s %(channel)s" % version
        feature_tuples.append((name, feature_file))
    return feature_tuples


# FIXME: This only really needs feature_files.
def stale_features(tuples):
    last_features = None
    can_be_removed = set()
    for _, feature_file in tuples:
        features = stable_features(feature_file)
        if last_features:
            can_be_removed.update(set(features))
            removed_features = list(set(last_features) - set(features))
            can_be_removed.difference_update(set(removed_features))
        last_features = features
    return sorted(can_be_removed)


def main():
    historical_tuples = historical_feature_tuples("win", "stable")
    active_tuples = active_feature_tuples("win")

    auditor = FeatureAuditor()
    for version, feature_file in historical_tuples + active_tuples:
        auditor.add_version(version, feature_file)

    print "\nConsider for removal (have been stable for at least one release):"
    for feature in stale_features(historical_tuples):
        print feature


if __name__ == "__main__":
    main()