async def test_bump_version_esr_dont_bump_non_esr(mocker, config, tmpdir, new_version, expect_esr_version): version = "52.0.1" repo = os.path.join(tmpdir, "repo") os.mkdir(repo) os.mkdir(os.path.join(repo, "config")) os.makedirs(os.path.join(repo, "browser", "config")) version_file = os.path.join("config", "milestone.txt") with open(os.path.join(repo, version_file), "w") as f: f.write(version) display_version_file = os.path.join("browser", "config", "version_display.txt") with open(os.path.join(repo, display_version_file), "w") as f: f.write(version + "esr") called_args = [] async def run_command(context, *arguments, repo_path=None): called_args.append([tuple([context]) + arguments, {"repo_path": repo_path}]) relative_files = [os.path.join("browser", "config", "version_display.txt"), os.path.join("config", "milestone.txt")] bump_info = {"files": relative_files, "next_version": new_version} mocked_bump_info = mocker.patch.object(vmanip, "get_version_bump_info") mocked_bump_info.return_value = bump_info mocker.patch.object(vmanip, "run_hg_command", new=run_command) await vmanip.bump_version(config, {}, repo) assert expect_esr_version == vmanip.get_version(display_version_file, repo) assert new_version == vmanip.get_version(version_file, repo) assert len(called_args) == 1
async def test_bump_version_esr_dont_bump_non_esr(mocker, config, tmpdir, new_version, expect_esr_version): version = "52.0.1" repo = os.path.join(tmpdir, "repo") os.mkdir(repo) os.mkdir(os.path.join(repo, "config")) os.makedirs(os.path.join(repo, "browser", "config")) version_file = os.path.join("config", "milestone.txt") with open(os.path.join(repo, version_file), "w") as f: f.write(version) display_version_file = os.path.join("browser", "config", "version_display.txt") with open(os.path.join(repo, display_version_file), "w") as f: f.write(version + "esr") relative_files = [ os.path.join("browser", "config", "version_display.txt"), os.path.join("config", "milestone.txt") ] bump_info = {"files": relative_files, "next_version": new_version} mocked_bump_info = mocker.patch.object(vmanip, "get_version_bump_info") mocked_bump_info.return_value = bump_info vcs_mock = AsyncMock() mocker.patch.object(vmanip, "get_vcs_module", return_value=vcs_mock) await vmanip.bump_version(config, {}, repo, repo_type="hg") assert expect_esr_version == vmanip.get_version(display_version_file, repo) assert new_version == vmanip.get_version(version_file, repo) vcs_mock.commit.assert_called_once()
async def get_revision_info(bump_config, repo_path): """Query the l10n changesets from the l10n dashboard. Args: bump_config (dict): one of the dictionaries from the payload ``l10n_bump_info`` repo_path (str): the path to the source repo Returns: str: the contents of the dashboard """ version = get_version(bump_config["version_path"], repo_path) repl_dict = { "MAJOR_VERSION": version.major_number, "COMBINED_MAJOR_VERSION": version.major_number + version.minor_number } url = bump_config["revision_url"] % repl_dict with tempfile.NamedTemporaryFile() as fp: path = fp.name await retry_async(download_file, args=(url, path), retry_exceptions=(DownloadError, )) with open(path, "r") as fh: revision_info = fh.read() log.info("Got %s", revision_info) return revision_info
async def test_bump_version_esr(mocker, repo_context, new_version, expect_version): if not repo_context.xtest_version.endswith("esr"): # XXX pytest.skip raised exceptions here for some reason. return called_args = [] async def run_command(context, *arguments, repo_path=None): called_args.append( [tuple([context]) + arguments, { "repo_path": repo_path }]) relative_files = [os.path.join("config", "milestone.txt")] bump_info = {"files": relative_files, "next_version": new_version} mocked_bump_info = mocker.patch.object(vmanip, "get_version_bump_info") mocked_bump_info.return_value = bump_info mocker.patch.object(vmanip, "run_hg_command", new=run_command) await vmanip.bump_version(repo_context.config, repo_context.task, repo_context.repo) assert expect_version == vmanip.get_version(relative_files[0], repo_context.repo) assert len(called_args) == 1 assert "repo_path" in called_args[0][1] assert is_slice_in_list(("commit", "-m"), called_args[0][0])
def create_new_version(version_config, repo_path): """Create the new version string used in file manipulation. Arguments: version_config (dict): { "filename": mandatory path, "new_suffix": string, default is to keep original. "version_bump": string, optional, enum 'major', 'minor' } Returns: string: new version string for file contents. """ version = get_version(version_config["filename"], repo_path) if version_config.get("version_bump") == "major": version = version.bump("major_number") elif version_config.get("version_bump") == "minor": version = version.bump("minor_number") if "new_suffix" in version_config: # '' is a valid entry version = attr.evolve(version, is_esr=False, beta_number=None, is_nightly=False) version = f"{version}{version_config['new_suffix']}" else: version = f"{version}" log.info("New version is %s", version) return version
async def apply_rebranding(config, repo_path, merge_config): """Apply changes to repo required for merge/rebranding.""" log.info("Rebranding %s to %s", merge_config.get("from_branch"), merge_config.get("to_branch")) # Must collect this before any bumping. version = get_version(core_version_file(merge_config), repo_path) # Used in file replacements, further down. format_options = { "current_major_version": version.major_number, "next_major_version": version.major_number + 1, "current_weave_version": version.major_number + 2, "next_weave_version": version.major_number + 3, # current_weave_version + 1 } if merge_config.get("version_files"): for version_config in merge_config["version_files"]: await do_bump_version(config, repo_path, [version_config["filename"]], create_new_version(version_config, repo_path)) for f in merge_config.get("copy_files", list()): shutil.copyfile(os.path.join(repo_path, f[0]), os.path.join(repo_path, f[1])) # Cope with bash variables in strings that we don't want to # be formatted in Python. We do this by ignoring {vars} we # aren't given keys for. fmt = BashFormatter() for f, from_, to in merge_config.get("replacements", list()): from_ = fmt.format(from_, **format_options) to = fmt.format(to, **format_options) replace(os.path.join(repo_path, f), from_, to) touch_clobber_file(config, repo_path)
async def test_bump_version(mocker, repo_context, new_version, should_append_esr): called_args = [] async def run_command(context, *arguments, repo_path=None): called_args.append( [tuple([context]) + arguments, { "repo_path": repo_path }]) test_version = new_version if repo_context.xtest_version.endswith("esr") and should_append_esr: test_version = new_version + "esr" relative_files = [os.path.join("config", "milestone.txt")] bump_info = {"files": relative_files, "next_version": new_version} mocked_bump_info = mocker.patch.object(vmanip, "get_version_bump_info") mocked_bump_info.return_value = bump_info mocker.patch.object(vmanip, "run_hg_command", new=run_command) await vmanip.bump_version(repo_context.config, repo_context.task, repo_context.repo) assert test_version == vmanip.get_version(relative_files[0], repo_context.repo) assert len(called_args) == 1 assert "repo_path" in called_args[0][1] assert is_slice_in_list(("commit", "-m"), called_args[0][0])
async def test_bump_version_smaller_version(mocker, repo_context, new_version): relative_files = [os.path.join("config", "milestone.txt")] bump_info = {"files": relative_files, "next_version": new_version} mocked_bump_info = mocker.patch.object(vmanip, "get_version_bump_info") mocked_bump_info.return_value = bump_info vcs_mock = AsyncMock() mocker.patch.object(vmanip, "get_vcs_module", return_value=vcs_mock) await vmanip.bump_version(repo_context.config, repo_context.task, repo_context.repo, repo_type="hg") assert repo_context.xtest_version == vmanip.get_version( relative_files[0], repo_context.repo) vcs_mock.commit.assert_not_called()
async def test_bump_version_same_version(mocker, repo_context): called_args = [] async def run_command(context, *arguments, repo_path=None): called_args.append([tuple([context]) + arguments, {"repo_path": repo_path}]) relative_files = [os.path.join("config", "milestone.txt")] bump_info = {"files": relative_files, "next_version": repo_context.xtest_version} mocked_bump_info = mocker.patch.object(vmanip, "get_version_bump_info") mocked_bump_info.return_value = bump_info mocker.patch.object(vmanip, "run_hg_command", new=run_command) await vmanip.bump_version(repo_context.config, repo_context.task, repo_context.repo) assert repo_context.xtest_version == vmanip.get_version(relative_files[0], repo_context.repo) assert len(called_args) == 0
async def test_bump_version_missing_file(mocker, repo_context, new_version): called_args = [] async def run_command(context, *arguments, repo_path=None): called_args.append([tuple([context]) + arguments, {"repo_path": repo_path}]) # Test only creates config/milestone.txt relative_files = [os.path.join("browser", "config", "version_display.txt"), os.path.join("config", "milestone.txt")] bump_info = {"files": relative_files, "next_version": new_version} mocked_bump_info = mocker.patch.object(vmanip, "get_version_bump_info") mocked_bump_info.return_value = bump_info mocker.patch.object(vmanip, "run_hg_command", new=run_command) with pytest.raises(TaskVerificationError): await vmanip.bump_version(repo_context.config, repo_context.task, repo_context.repo) assert repo_context.xtest_version == vmanip.get_version(relative_files[1], repo_context.repo) assert len(called_args) == 0
async def test_bump_version_esr(mocker, repo_context, new_version, expect_version): if not repo_context.xtest_version.endswith("esr"): # XXX pytest.skip raised exceptions here for some reason. return relative_files = [os.path.join("config", "milestone.txt")] bump_info = {"files": relative_files, "next_version": new_version} mocked_bump_info = mocker.patch.object(vmanip, "get_version_bump_info") mocked_bump_info.return_value = bump_info vcs_mock = AsyncMock() mocker.patch.object(vmanip, "get_vcs_module", return_value=vcs_mock) await vmanip.bump_version(repo_context.config, repo_context.task, repo_context.repo, repo_type="hg") assert expect_version == vmanip.get_version(relative_files[0], repo_context.repo) vcs_mock.commit.assert_called_once()
async def test_bump_version_missing_file(mocker, repo_context, new_version): # Test only creates config/milestone.txt relative_files = [ os.path.join("browser", "config", "version_display.txt"), os.path.join("config", "milestone.txt") ] bump_info = {"files": relative_files, "next_version": new_version} mocked_bump_info = mocker.patch.object(vmanip, "get_version_bump_info") mocked_bump_info.return_value = bump_info vcs_mock = AsyncMock() mocker.patch.object(vmanip, "get_vcs_module", return_value=vcs_mock) with pytest.raises(TaskVerificationError): await vmanip.bump_version(repo_context.config, repo_context.task, repo_context.repo, repo_type="hg") assert repo_context.xtest_version == vmanip.get_version( relative_files[1], repo_context.repo) vcs_mock.commit.assert_not_called()
async def test_bump_version(mocker, repo_context, new_version, should_append_esr): test_version = new_version if repo_context.xtest_version.endswith("esr") and should_append_esr: test_version = new_version + "esr" relative_files = [os.path.join("config", "milestone.txt")] bump_info = {"files": relative_files, "next_version": new_version} mocked_bump_info = mocker.patch.object(vmanip, "get_version_bump_info") mocked_bump_info.return_value = bump_info vcs_mock = AsyncMock() mocker.patch.object(vmanip, "get_vcs_module", return_value=vcs_mock) await vmanip.bump_version(repo_context.config, repo_context.task, repo_context.repo, repo_type="hg") assert test_version == vmanip.get_version(relative_files[0], repo_context.repo) assert vcs_mock.commit.call_args_list[0][0][ 2] == "Automatic version bump CLOSED TREE NO BUG a=release"
async def apply_rebranding(config, repo_path, merge_config): """Apply changes to repo required for merge/rebranding.""" log.info("Rebranding %s to %s", merge_config.get("from_branch"), merge_config.get("to_branch")) version = get_version("browser/config/version.txt", repo_path) current_major_version = version.major_number if merge_config.get("incr_major_version", False): version = version.bump("major_number") if merge_config.get("version_files"): next_version = f"{version.major_number}.{version.minor_number}" await do_bump_version(config, repo_path, merge_config["version_files"], next_version) if merge_config.get("version_files_suffix"): version = attr.evolve(version, is_esr=False, beta_number=None, is_nightly=False) next_version = f"{version}{merge_config.get('version_suffix')}" await do_bump_version(config, repo_path, merge_config["version_files_suffix"], next_version) for f in merge_config.get("copy_files", list()): shutil.copyfile(os.path.join(repo_path, f[0]), os.path.join(repo_path, f[1])) format_options = { "current_major_version": current_major_version, "next_major_version": version.major_number, "current_weave_version": current_major_version + 2, "next_weave_version": current_major_version + 3, # current_weave_version + 1 } # Cope with bash variables in strings that we don't want to # be formatted in Python. We do this by ignoring {vars} we # aren't given keys for. fmt = BashFormatter() for f, from_, to in merge_config.get("replacements", list()): from_ = fmt.format(from_, **format_options) to = fmt.format(to, **format_options) replace(os.path.join(repo_path, f), from_, to) touch_clobber_file(config, repo_path)
async def do_merge(config, task, repo_path): """Perform a merge day operation. This function takes its inputs from task's payload. Args: config (dict): the running config task (dict): the running task repo_path (str): the source directory Raises: TaskverificationError: from get_merge_config if the payload is invalid. Returns: list: A list of the branches that need pushing, and the corresponding revision. This is unlike other actions as the list of outgoing changes is not related to the number of commands we've performed, but we do need to know which branches to push. """ merge_config = get_merge_config(task) from_branch = merge_config.get("from_branch") to_branch = merge_config.get("to_branch") await run_hg_command(config, "pull", "https://hg.mozilla.org/mozilla-unified", repo_path=repo_path) # Used if end_tag is set. await run_hg_command(config, "up", "-C", to_branch, repo_path=repo_path) to_fx_major_version = get_version("browser/config/version.txt", repo_path).major_number base_to_rev = await get_revision(config, repo_path, branch=to_branch) if from_branch: await run_hg_command(config, "up", "-C", from_branch, repo_path=repo_path) base_from_rev = await get_revision(config, repo_path, branch=from_branch) base_tag = merge_config.get("base_tag") if base_tag: base_tag = base_tag.format(major_version=get_version( "browser/config/version.txt", repo_path).major_number) tag_message = f"No bug - tagging {base_from_rev} with {base_tag} a=release DONTBUILD CLOSED TREE" await run_hg_command(config, "tag", "-m", tag_message, "-r", base_from_rev, "-f", base_tag, repo_path=repo_path) tagged_from_rev = await get_revision(config, repo_path, branch=".") # TODO This shouldn't be run on esr, according to old configs. # perhaps: hg push -r bookmark("release") esrNN # Perform the kludge-merge. if merge_config.get("merge_old_head", False): await run_hg_command(config, "debugsetparents", tagged_from_rev, base_to_rev, repo_path=repo_path) await run_hg_command( config, "commit", "-m", "Merge old head via |hg debugsetparents {} {}| CLOSED TREE DONTBUILD a=release" .format(tagged_from_rev, base_to_rev), repo_path=repo_path, ) await preserve_tags(config, repo_path, to_branch) end_tag = merge_config.get("end_tag") # tag the end of the to repo if end_tag: end_tag = end_tag.format(major_version=to_fx_major_version) tag_message = f"No bug - tagging {base_to_rev} with {end_tag} a=release DONTBUILD CLOSED TREE" await run_hg_command(config, "tag", "-m", tag_message, "-r", base_to_rev, "-f", end_tag, repo_path=repo_path) await apply_rebranding(config, repo_path, merge_config) diff_output = await run_hg_command(config, "diff", repo_path=repo_path, return_output=True) path = os.path.join(config["artifact_dir"], "public", "logs", "{}.diff".format(to_branch)) makedirs(os.path.dirname(path)) with open(path, "w") as fh: fh.write(diff_output) await run_hg_command( config, "commit", "-m", "Update configs. IGNORE BROKEN CHANGESETS CLOSED TREE NO BUG a=release ba=release", repo_path=repo_path) push_revision_to = await get_revision(config, repo_path, branch=".") # Do we need to perform multiple pushes for the push stage? If so, return # what to do. desired_pushes = list() if merge_config.get("from_repo"): desired_pushes.append((merge_config["from_repo"], tagged_from_rev)) if merge_config.get("to_repo"): desired_pushes.append((merge_config["to_repo"], push_revision_to)) return desired_pushes
def test_replace_ver_in_file(repo_context, new_version): filepath = "config/milestone.txt" old_ver = repo_context.xtest_version vmanip.replace_ver_in_file(os.path.join(repo_context.repo, filepath), old_ver, new_version) assert new_version == vmanip.get_version(filepath, repo_context.repo)
def test_get_version(repo_context): ver = vmanip.get_version("config/milestone.txt", repo_context.repo) assert ver == repo_context.xtest_version