Пример #1
0
    def test_bump(self, current, bump_part, expected, stages):
        with mock_patch.object(Version, "stages", stages):
            version = Version(current)
            new_version = version.bump(bump_part)

            assert new_version == Version(expected)
            assert new_version is not version
Пример #2
0
def test_get_next_version(current, bump_to, stage, expected, stages):
    with mock_patch.object(Version, "stages", stages):
        current = Version(current)
        expected = Version(expected) if expected else None

        new_version = get_next_version(current, bump_to, stage)

        assert new_version == expected
Пример #3
0
    def test_set_stages_with_invalid_serializer(self):
        stages = [
            ("alpha", "{major}.{minor}.{patch}a{n}"),
            ("beta", "{major}.{patch}beta{n}"),
            ("final", "{major}.{minor}.{patch}"),
        ]

        message = "{major}.{patch}beta{n} is an invalid serializer"

        with pytest.raises(ValueError, match=message):
            Version.set_stages(stages)
Пример #4
0
    def test_set_stages(self, stages):
        _stages = [
            ("dev", "{major}.{minor}.{patch}.dev{n}"),
            ("beta", "{major}.{minor}.{patch}beta{n}"),
            ("final", "{major}.{minor}.{patch}"),
        ]

        with mock_patch.object(Version, "stages", {}):
            Version.set_stages(_stages)

            assert Version.stages == stages
Пример #5
0
def test_tag_analyzer():

    tags = [
        Tag("2016-10-15   v10.0.1"),
        Tag("2016-08-26   v0.10.13"),
        Tag("2016-05-06   save-point"),
        Tag("2016-04-26   v0.9.7"),
    ]

    result = tag_analyzer(tags, "v{version}", Version)

    assert result == [Version("10.0.1"), Version("0.10.13"), Version("0.9.7")]

    assert result[0].tag.name == "v10.0.1"
Пример #6
0
def test_update_chglog(mock_render_release, isolated_filesystem, file_content,
                       expected):
    cur_version = Version("1.0.0")
    new_version = Version("2.0.0")

    with isolated_filesystem:
        path = Path.cwd() / "CHANGELOG.rst"
        path.write_text(file_content)

        update_chglog(path, cur_version, new_version, {})

        mock_render_release.assert_called_with(new_version, {})

        text = path.read_text()

        assert text == expected
Пример #7
0
def test_manual_version_bump(
    MockGit,
    mock_update_chglog,
    option,
    tags,
    current_version,
    expected,
    isolated_filesystem,
):

    mock_git = MockGit()
    mock_git.tags = tags
    runner = CliRunner()

    with isolated_filesystem("HISTORY.rst"):
        result = runner.invoke(cli, ["release", option], input="y")

        assert result.exit_code == 0

        # Check what version was passed to update_chglog function
        mock_update_chglog.assert_called_with(
            Path("HISTORY.rst"),
            current_version=current_version,
            new_version=Version(expected),
            release_data={},
            remove=ANY,
        )

        mock_git.commit.assert_called_with(
            f"Release version {expected}", files=["HISTORY.rst"]
        )
        mock_git.tag.assert_called_with(f"v{expected}")
Пример #8
0
def test_determine_next_version_from_commit_messages(
    MockGit,
    mocked_update_changelog,
    hash_lst,
    tags,
    expected,
    commit_registry,
    isolated_filesystem,
):
    mock_git = MockGit()
    mock_git.tags = tags
    mock_git.log.return_value = [commit_registry[short_hash] for short_hash in hash_lst]

    runner = CliRunner()

    with isolated_filesystem("HISTORY.rst"):
        result = runner.invoke(cli, ["release", "-y"])

        assert result.exit_code == 0, result.exception

        mocked_update_changelog.assert_called()

        # Check what version was passed to update_chglog function
        passed_args = mocked_update_changelog.call_args[1]
        assert passed_args["new_version"] == Version(expected)

        mock_git.commit.assert_called_with(
            f"Release version {expected}", files=["HISTORY.rst"]
        )
        mock_git.tag.assert_called_with(f"v{expected}")
Пример #9
0
def current_version_option_validator(ctx, param, value):
    """If a version string is provided, validates it. Otherwise it tries
    to determine the current version from the last Git tag that matches
    ``tag_pattern`` option.

    Return a :class:`~braulio.version.Version` object or **None**.
    """

    current_version = None

    if value:
        try:
            current_version = Version(value)
        except ValueError:
            ctx.fail(f"{value} is not a valid version string")

    # Look for the last git tag for the curren version
    git = Git()
    tag_pattern = ctx.params["tag_pattern"]
    versions = tag_analyzer(git.tags, tag_pattern, Version)

    # User provided current version. Try to find a tag that match it.
    if current_version:
        for version in versions:
            if version == current_version:
                current_version = version
                break
    elif versions:
        current_version = versions[0]

    ctx.params[
        "current_tag"] = current_version.tag if current_version else None
    ctx.params["versions"] = versions

    return current_version
Пример #10
0
    def test_default(self):
        version = Version()

        assert version.major == 0
        assert version.minor == 0
        assert version.patch == 0
        assert version.string == "0.0.0"
        assert version.stage == "final"
        assert version.n is 0
Пример #11
0
    def test_release_with_fixes(self, commit_registry):
        semantic_commits = commit_analyzer([commit_registry["4d17c1a"]],
                                           label_pattern="!{type}:{scope}")
        release_data = ReleaseDataTree(semantic_commits)
        version = Version(major=10, minor=3, patch=0)

        markup = _render_release(version, release_data=release_data)

        assert markup == (f"10.3.0 ({str(date.today())})\n"
                          "-------------------\n\n"
                          "Bug Fixes\n"
                          "~~~~~~~~~\n\n"
                          "* thing - Fix a thing\n\n")
Пример #12
0
def test_call_to_update_changelog(
    MockGit,
    mock_get_next_version,
    mock_update_chglog,
    mock_commit_analyzer,
    MockReleaseDataTree,
    isolated_filesystem,
):
    runner = CliRunner()
    mock_git = MockGit()
    mock_git.tags = []

    with isolated_filesystem("HISTORY.rst"):
        result = runner.invoke(cli, ["release", "-y"])

        assert result.exit_code == 0

        mock_git.log.assert_called()

        mock_commit_analyzer.assert_called_with(
            mock_git.log(), "!{type}:{scope}", "footer"
        )

        MockReleaseDataTree.assert_called_with(mock_commit_analyzer())

        release_data = MockReleaseDataTree()

        mock_get_next_version.assert_called_with(
            Version(), release_data.bump_version_to, None
        )

        mock_update_chglog.assert_called_with(
            Path("HISTORY.rst"),
            current_version=Version(),
            new_version=mock_get_next_version(),
            release_data=release_data,
            remove=ANY,
        )
Пример #13
0
def test_remove_block_from_changelog(isolated_filesystem):
    cur_version = Version("1.0.0")
    new_version = Version("2.0.0")

    with isolated_filesystem:
        path = Path.cwd() / "CHANGELOG.rst"
        path.write_text("line 1\n"
                        "line 2\n"
                        "line 3\n"
                        "line 4\n"
                        "line 5\n"
                        "line 6\n")

        update_chglog(path, cur_version, new_version, {}, ["line 2", "line 5"])

        text = path.read_text()

        assert "line 1" in text
        assert "line 2" not in text
        assert "line 3" not in text
        assert "line 4" not in text
        assert "line 5" in text
        assert "line 6" in text
Пример #14
0
def cli(ctx):

    config = Config()
    ctx.obj = config

    try:
        Version.set_stages(config.stages.items())
    except ValueError as e:
        ctx.fail(e)

    ctx.default_map = {
        "release": {
            "tag_flag": config.tag,
            "commit_flag": config.commit,
            "confirm_flag": config.confirm,
            "label_position": config.label_position,
            "label_pattern": config.label_pattern,
            "tag_pattern": config.tag_pattern,
            "current_version": config.current_version,
            "changelog_file": config.changelog_file,
            "message": config.message,
        }
    }
Пример #15
0
def bump_option_validator(ctx, param, value):
    """In case a value is provided checks that it is a valid version string. If
    is not thrown :class:`click.UsageError`.

    Return a :class:`~braulio.version.Version` object or **None**.
    """

    if value:
        try:
            value = Version(value)
        except ValueError:
            ctx.fail(f"{x_mark} {value} is not a valid version string")

    return value
Пример #16
0
    def test_deserializing(self, string, minor, major, patch, stage, n):
        stages = OrderedDict(
            dev=Stage("dev", "{major}.{minor}.{patch}dev{n}"),
            a=Stage("alpha", "{major}.{minor}.{patch}a{n}"),
            final=Stage("final", "{major}.{minor}.{patch}"),
        )

        with mock_patch.object(Version, "stages", stages):
            version = Version(string)

            assert version.major == major
            assert version.minor == minor
            assert version.patch == patch
            assert version.stage == stage
            assert version.n == n
Пример #17
0
    def test_release_with_features(self, commit_registry):
        reg = commit_registry
        version = Version(major=10, minor=3, patch=0)
        semantic_commits = commit_analyzer(
            [reg["ccaa185"], reg["bc0bcab"], reg["a6b655f"]],
            label_pattern="!{type}:{scope}",
        )
        release_data = ReleaseDataTree(semantic_commits)
        markup = _render_release(version, release_data=release_data)

        assert markup == (f"10.3.0 ({str(date.today())})\n"
                          "-------------------\n\n"
                          "Features\n"
                          "~~~~~~~~\n\n"
                          "* Add a thing\n"
                          "* music - Add more music please\n"
                          "* cli - Add a cli tool\n\n")
Пример #18
0
    def test_release_with_fixes_and_features(self, commit_list):
        version = Version(major=10, minor=3, patch=0)
        semantic_commits = commit_analyzer(commit_list,
                                           label_pattern="!{type}:{scope}")
        release_data = ReleaseDataTree(semantic_commits)
        markup = _render_release(version, release_data=release_data)

        assert markup == (f"10.3.0 ({str(date.today())})\n"
                          "-------------------\n\n"
                          "Bug Fixes\n"
                          "~~~~~~~~~\n\n"
                          "* thing - Fix a thing\n\n"
                          "Features\n"
                          "~~~~~~~~\n\n"
                          "* Add a thing\n"
                          "* music\n\n"
                          "  - Add more music please\n"
                          "  - Add cool musics :D\n"
                          "* cli - Add a cli tool\n\n")
Пример #19
0
    def test_default_serializers(self, major, minor, patch, expected):
        version = Version(major=major, minor=minor, patch=patch)

        assert version.string == expected
Пример #20
0
    def test_unknown_stage(self):
        error = "gama is an unknown stage"

        with pytest.raises(ValueError, match=error):
            Version("2.0.7gama0")
Пример #21
0
def release(
    ctx,
    bump,
    bump_type,
    commit_flag,
    message,
    tag_flag,
    confirm_flag,
    changelog_file,
    files,
    label_pattern,
    label_position,
    tag_pattern,
    current_version,
    stage,
    merge_pre,
    current_tag=None,
    versions=None,
):
    """Release a new version.

    Determines the next version by inspecting commit messages, updates the
    changelog, commit the changes and tag the repository with the new version.
    """
    # If there isn't a current version, assume version 0.0.0
    current_version = current_version or Version()

    git = Git()
    from_tag = current_tag.name if current_tag else None

    # Delimiter of the block to be removed from the changelog file
    remove_pre_chglog = None

    # Look for the last final release version if the user want it
    if merge_pre and current_version.stage != "final":
        remove_pre_chglog = [current_version.string]

        for version in versions:
            if version.stage == "final":
                from_tag = version.tag.name
                remove_pre_chglog.append(version.string)
                break

    commit_list = git.log(_from=from_tag)

    msg(f'{label("Current version")} {current_version}')
    msg(f'{label("Commits found")} {len(commit_list)} since last release')

    if not commit_list:
        click.echo(" › Nothing to release.")
        ctx.exit()

    semantic_commits = commit_analyzer(commit_list, label_pattern,
                                       label_position)

    release_data = ReleaseDataTree(semantic_commits)

    bump_version_to = None

    # --bump, --major, --minor, --patch or commit message based version
    # are taken into account only if the current version is in final stage.
    if current_version.stage == "final":

        # --bump have precedence over any of --major, --minor or --patch
        bump_version_to = bump.string if bump else bump_type

        # Any manual bump have precedence over commit message based versions.
        bump_version_to = bump_version_to or release_data.bump_version_to

    try:
        new_version = get_next_version(current_version, bump_version_to, stage)
    except ValueError as e:
        ctx.fail(e)

    if not new_version:
        msg("The release of a lower versions is not supported for now.")
        ctx.abort()

    new_tag_name = tag_pattern.format(version=new_version.string)

    msg(f'{label("New version")} {new_version}')
    msg(f'{label("Changelog file")} {changelog_file.name}')

    # Messages about what tasks will be performed
    msg("Braulio will perform the next tasks :")
    msg(f"        Update {len(files) + 1} files.", prefix="")
    msg("        Add a release commit.", prefix="", silence=not commit_flag)
    msg(
        f"        Tag the repository with {new_tag_name}",
        prefix="",
        silence=not tag_flag,
    )

    msg("", prefix="")  # Print just a new line

    if confirm_flag or click.confirm(f"{prefix_mark}Continue?"):

        msg("Update changelog ", nl=False)

        update_chglog(
            changelog_file,
            new_version=new_version,
            current_version=current_version,
            release_data=release_data,
            remove=remove_pre_chglog,
        )

        msg(check_mark, prefix="")

        try:
            update_files(files, str(current_version), str(new_version))
        except ValueError as e:
            click.echo(e)
            ctx.abort()

        if commit_flag:
            message_args = {"new_version": new_version.string}

            if "{current_version}" in message:
                message_args["current_version"] = current_version.string

            commit_message = message.format(**message_args)

            msg(f"Add commit: {commit_message}", nl=False)

            files = [str(changelog_file)] + list(files)
            git.commit(commit_message, files=files)
            msg(f" {check_mark}", prefix="")

        if tag_flag:
            msg(f"Add tag {new_tag_name}", nl=False)
            git.tag(new_tag_name)
            msg(f" {check_mark}", prefix="")

        if "current_version" in ctx.obj.cfg_file_options:
            update_config_file("current_version", new_version.string)

        msg(f"Version {new_version} released successfully", suffix=" 🎉")
Пример #22
0
 def test_invalid_bump(self, current, bump_part, message, stages):
     with mock_patch.object(Version, "stages", stages):
         with pytest.raises(ValueError, match=message):
             version = Version(current)
             version.bump(bump_part)
Пример #23
0
 def test_repr(self):
     assert repr(Version("3.2.1")) == "Version('3.2.1')"
Пример #24
0
 def test_str(self):
     assert str(Version("3.2.1")) == "3.2.1"
Пример #25
0
def test_comparison_operators(left, right, is_less, is_greater, is_equal, stages):
    with mock_patch.object(Version, "stages", stages):
        assert (Version(left) < Version(right)) is is_less
        assert (Version(left) > Version(right)) is is_greater
        assert (Version(left) == Version(right)) is is_equal
        assert (Version(left) <= Version(right)) is (is_less or is_equal)
        assert (Version(left) >= Version(right)) is (is_greater or is_equal)
        assert (Version(left) != Version(right)) is not is_equal
Пример #26
0
class TestVersion:
    def test_default(self):
        version = Version()

        assert version.major == 0
        assert version.minor == 0
        assert version.patch == 0
        assert version.string == "0.0.0"
        assert version.stage == "final"
        assert version.n is 0

    def test_invalid_version_string_argument(self):
        with pytest.raises(ValueError):
            Version("invalid")

    @parametrize(
        "string, major, minor, patch, stage, n",
        [
            ("1.2.3", 1, 2, 3, "final", 0),
            ("1", 1, 0, 0, "final", 0),
            ("2.1a2", 2, 1, 0, "a", 2),
            ("3.1.4.dev0", 3, 1, 4, "dev", 0),
        ],
    )
    def test_deserializing(self, string, minor, major, patch, stage, n):
        stages = OrderedDict(
            dev=Stage("dev", "{major}.{minor}.{patch}dev{n}"),
            a=Stage("alpha", "{major}.{minor}.{patch}a{n}"),
            final=Stage("final", "{major}.{minor}.{patch}"),
        )

        with mock_patch.object(Version, "stages", stages):
            version = Version(string)

            assert version.major == major
            assert version.minor == minor
            assert version.patch == patch
            assert version.stage == stage
            assert version.n == n

    @parametrize(
        "major, minor, patch, expected", [(1, 2, 3, "1.2.3"), (1, 0, 0, "1.0.0")]
    )
    def test_default_serializers(self, major, minor, patch, expected):
        version = Version(major=major, minor=minor, patch=patch)

        assert version.string == expected

    @parametrize(
        "major, minor, patch, stage, n, expected",
        [
            (1, 2, 3, None, 0, "1.2.3"),
            (2, 1, 0, "dev", 2, "2.1.0.dev2"),
            (2, 1, 0, "beta", 1, "2.1.0beta1"),
        ],
    )
    def test_custom_serializers(self, major, minor, patch, stage, n, expected, stages):
        with mock_patch.object(Version, "stages", stages):
            version = Version(major=major, minor=minor, patch=patch, stage=stage, n=n)

            assert version.string == expected

    def test_unknown_stage(self):
        error = "gama is an unknown stage"

        with pytest.raises(ValueError, match=error):
            Version("2.0.7gama0")

    def test_set_stages(self, stages):
        _stages = [
            ("dev", "{major}.{minor}.{patch}.dev{n}"),
            ("beta", "{major}.{minor}.{patch}beta{n}"),
            ("final", "{major}.{minor}.{patch}"),
        ]

        with mock_patch.object(Version, "stages", {}):
            Version.set_stages(_stages)

            assert Version.stages == stages

    def test_set_stages_with_invalid_serializer(self):
        stages = [
            ("alpha", "{major}.{minor}.{patch}a{n}"),
            ("beta", "{major}.{patch}beta{n}"),
            ("final", "{major}.{minor}.{patch}"),
        ]

        message = "{major}.{patch}beta{n} is an invalid serializer"

        with pytest.raises(ValueError, match=message):
            Version.set_stages(stages)

    @parametrize(
        "current, bump_part, expected",
        [
            ("1.2.3", "major", "2.0.0"),
            ("1.2.3", "minor", "1.3.0"),
            ("1.2.3", "patch", "1.2.4"),
            ("1.2.3.dev0", "dev", "1.2.3.dev1"),
            ("1.2.3.dev5", "beta", "1.2.3beta0"),
            ("1.2.3.dev5", "final", "1.2.3"),
            ("1.2.3beta0", "final", "1.2.3"),
        ],
        ids=[
            "major",
            "minor",
            "patch",
            "stage numerical component",
            "to next stage",
            "from pre-release stage to final stage",
            "from first stage to final stage",
        ],
    )
    def test_bump(self, current, bump_part, expected, stages):
        with mock_patch.object(Version, "stages", stages):
            version = Version(current)
            new_version = version.bump(bump_part)

            assert new_version == Version(expected)
            assert new_version is not version

    @parametrize(
        "current, bump_part, message",
        [
            ("1.2.3beta0", "dev", "dev stage is previous to 1.2.3beta0"),
            ("1.2.3beta0", "major", "Can't do a major version bump"),
            ("1.2.3beta0", "minor", "Can't do a minor version bump"),
            ("1.2.3beta0", "patch", "Can't do a patch version bump"),
            ("1.2.3", "final", "1.2.3 is already in final stage"),
            ("1.2.3", "unknown", "Unknown unknown stage"),
        ],
        ids=[
            "to previous stage",
            "major part in pre-release stage",
            "minor part in pre-release stage",
            "patch part in pre-release stage",
            "from final to final stage",
            "unknown stage",
        ],
    )
    def test_invalid_bump(self, current, bump_part, message, stages):
        with mock_patch.object(Version, "stages", stages):
            with pytest.raises(ValueError, match=message):
                version = Version(current)
                version.bump(bump_part)

    @parametrize(
        "left, right",
        [
            (Version(), 1),
            (Version(), "2.23.8"),
            (Version(), None),
            (Version(), True),
            (1, Version()),
            ("2.23.8", Version()),
            (None, Version()),
            (True, Version()),
        ],
    )
    def test_comparators_with_invalid_values(self, left, right):
        error_message = "not supported"

        with pytest.raises(TypeError, match=error_message):
            left > right

        with pytest.raises(TypeError, match=error_message):
            left < right

        with pytest.raises(TypeError, match=error_message):
            left <= right

        with pytest.raises(TypeError, match=error_message):
            left >= right

        with pytest.raises(TypeError, match=error_message):
            left == right

        with pytest.raises(TypeError, match=error_message):
            left != right

    def test_repr(self):
        assert repr(Version("3.2.1")) == "Version('3.2.1')"

    def test_str(self):
        assert str(Version("3.2.1")) == "3.2.1"
Пример #27
0
        result = runner.invoke(cli, ["release"], input=_input)

        assert result.exit_code == 0
        assert " › Continue? [y/N]" in result.output

        mock_git = MockGit()

        called = True if _input == "y" else False
        assert mock_update_chglog.called is called
        assert mock_git.commit.called is called
        assert mock_git.tag.called is called


@parametrize(
    "value, expected",
    [("6", Version("6.0.0")), ("4.3", Version("4.3.0")), ("3.2.1", Version("3.2.1"))],
)
def test_bump_option_validator(ctx, value, expected):
    assert bump_option_validator(ctx, {}, value) == expected


def test_bump_option_validator_with_invalid_string(ctx):
    with pytest.raises(UsageError):
        bump_option_validator(ctx, {}, "invalid-version-string")


@parametrize(
    "option, tags, current_version, expected",
    [
        ("--major", [FakeTag("v0.10.1")], Version("0.10.1"), "1.0.0"),
        ("--minor", [FakeTag("v0.10.1")], Version("0.10.1"), "0.11.0"),
Пример #28
0
 def test_invalid_version_string_argument(self):
     with pytest.raises(ValueError):
         Version("invalid")
Пример #29
0
    def test_custom_serializers(self, major, minor, patch, stage, n, expected, stages):
        with mock_patch.object(Version, "stages", stages):
            version = Version(major=major, minor=minor, patch=patch, stage=stage, n=n)

            assert version.string == expected