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 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
|
# Copyright 2021 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Shared helper functions for r/w of variations seed in local state.
# TODO(crbug.com/417138763): Update comment with new format.
The seed is can be stored in either the local state file or the seed file.
- When stored in the seed file, the seed is stored compressed with gzip format.
- When stored in the local state file, the seed is compressed with gzip format
and encoded into base64.
"""
import base64
import json
import logging
import os
_THIS_DIR = os.path.dirname(os.path.abspath(__file__))
_SRC_DIR = os.path.join(_THIS_DIR, os.path.pardir, os.path.pardir)
# Constants around the Local State file and variation keys.
_LOCAL_STATE_FILE_NAME = 'Local State'
_VARIATIONS_SEED_FILE_NAME = 'VariationsSeedV1'
_SEED_KEY = 'variations_compressed_seed'
_SEED_SIGNATURE_KEY = 'variations_seed_signature'
ILL_FORMED_TEST_SEED_ERROR_MESSAGE = (
f'Ill-formed test seed json file: "{_SEED_KEY}" and "{_SEED_SIGNATURE_KEY}"'
' are required')
def load_test_seed_from_file(hardcoded_seed_path):
"""Reads and parses the test variations seed.
There are 2 types of seeds used by this smoke test:
1. A provided seed under test, and when the test is running with this seed,
it's running as a TRY job and is triggered by the finch_smoke_test recipe
to test the Finch GCL config changes. The interface between the recipe and
this test is a json file named variations_seed located at the root of
the checkout.
2. A hard-coded seed, and when the test is running with this seed, it's
running on CI continuously to prevent regressions to this test itself.
The function tries to use provided seed first. If the provided seed doesn't
exist, the function will fallback to a hard-coded seed as input arg.
Args:
seed_json_path (str): Path to provided hard-coded seed.
Returns:
A tuple of two strings: the compressed seed and the seed signature.
"""
# Provided seed path.
path_seed = get_test_seed_file_path(hardcoded_seed_path)
logging.info('Parsing test seed from "%s"', path_seed)
with open(path_seed, 'r') as f:
seed_json = json.load(f)
return (seed_json.get(_SEED_KEY,
None), seed_json.get(_SEED_SIGNATURE_KEY, None))
def get_test_seed_file_path(hardcoded_seed_path):
"""Gets the file path to the test seed.
There are 2 types of seeds used by this smoke test:
1. A provided seed under test, and when the test is running with this seed,
it's running as a TRY job and is triggered by the finch_smoke_test recipe
to test the Finch GCL config changes. The interface between the recipe and
this test is a json file named variations_seed located at the root of
the checkout.
2. A hard-coded seed, and when the test is running with this seed, it's
running on CI continuously to prevent regressions to this test itself.
The function tries to use provided seed first. If the provided seed doesn't
exist, the function will fallback to a hard-coded seed as input arg.
Args:
hardcoded_seed_path (str): Path to provided hard-coded seed.
Returns:
A path to the location of the seed.
"""
path_seed = os.path.abspath(os.path.join(_SRC_DIR, 'variations_seed'))
if not os.path.isfile(path_seed):
path_seed = hardcoded_seed_path
return path_seed
# TODO(crbug.com/417138763): Update this function to support reading the seed
# and signature from the seed file with proto format.
def get_current_seed(user_data_dir):
"""Gets the current seed.
Args:
user_data_dir (str): Path to the user data directory used to launch Chrome.
Returns:
A tuple of two strings: the compressed seed and the seed signature.
"""
with open(os.path.join(user_data_dir, _LOCAL_STATE_FILE_NAME)) as f:
local_state = json.load(f)
# Try to read the seed from the seed file first. If the file doesn't exist,
# read from local state.
seed_file_path = os.path.join(user_data_dir, _VARIATIONS_SEED_FILE_NAME)
if os.path.exists(seed_file_path):
with open(seed_file_path, 'rb') as f:
seed_file = f.read()
# The seed was originally stored compressed and base64-encoded in Local
# State. We encode it again to match the expected format.
seed = base64.b64encode(seed_file).decode('ascii')
else:
# The seed from Local State is already compressed and base64-encoded.
seed = local_state.get(_SEED_KEY, None)
signature = local_state.get(_SEED_SIGNATURE_KEY, None)
return seed, signature
def _update_seed_file(user_data_dir, seed_dict):
"""Updates the seed in the seed file.
The seed is stored compressed with gzip format.
Args:
user_data_dir (str): Path to the user data directory used to launch Chrome.
seed_dict (dict): A dict used to update current seed file.
"""
# The seed is stored compressed with gzip format in the seed file.
decoded_seed = base64.b64decode(seed_dict.get(_SEED_KEY, None))
with open(os.path.join(user_data_dir, _VARIATIONS_SEED_FILE_NAME), 'wb') as f:
f.write(decoded_seed)
# The signature is stored in Local State.
# TODO(crbug.com/411431524): Store the signature in the seed file.
update_local_state(user_data_dir, {
_SEED_SIGNATURE_KEY: seed_dict.get(_SEED_SIGNATURE_KEY),
})
# TODO(crbug.com/417138763): Update this function to support updating the seed
# to the seed file with proto format.
def update_seed(user_data_dir, seed, signature):
"""Updates the seed in the local state and seed file.
Args:
user_data_dir (str): Path to the user data directory used to launch Chrome.
seed (str): A variations seed.
signature (str): A seed signature.
"""
seed_dict = {
_SEED_KEY: seed,
_SEED_SIGNATURE_KEY: signature,
}
update_local_state(user_data_dir, seed_dict)
_update_seed_file(user_data_dir, seed_dict)
def update_local_state(user_data_dir, update_dict):
"""Updates local state file under user data dir with given dict.
Args:
user_data_dir (str): Path to the user data directory used to launch Chrome.
update_dict (dict): A dict used to update current local state file.
"""
with open(os.path.join(user_data_dir, _LOCAL_STATE_FILE_NAME)) as f:
local_state = json.load(f)
local_state.update(update_dict)
with open(os.path.join(user_data_dir, _LOCAL_STATE_FILE_NAME), 'w') as f:
json.dump(local_state, f)
def inject_test_seed(seed, signature, user_data_dir):
"""Injects the given test seed.
Args:
seed (str): A variations seed.
signature (str): A seed signature.
user_data_dir (str): Path to the user data directory used to launch Chrome.
Returns:
bool: Whether the injection succeeded.
"""
update_seed(user_data_dir, seed, signature)
current_seed, current_signature = get_current_seed(user_data_dir)
if current_seed != seed or current_signature != signature:
return False
return True
|