001/*
002 * Licensed under the Apache License, Version 2.0 (the "License");
003 * you may not use this file except in compliance with the License.
004 * You may obtain a copy of the License at
005 *
006 * http://www.apache.org/licenses/LICENSE-2.0
007 *
008 * Unless required by applicable law or agreed to in writing, software
009 * distributed under the License is distributed on an "AS IS" BASIS,
010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011 * See the License for the specific language governing permissions and
012 * limitations under the License.
013 */
014
015package de.softwareforge.testing.postgres.junit5;
016
017import static com.google.common.base.Preconditions.checkNotNull;
018import static java.lang.String.format;
019
020import de.softwareforge.testing.postgres.embedded.EmbeddedPostgres;
021
022import java.io.IOException;
023import java.util.List;
024
025import com.google.auto.value.AutoValue;
026import com.google.common.base.Joiner;
027import com.google.common.base.Splitter;
028import com.google.common.collect.ComparisonChain;
029import org.junit.jupiter.api.extension.ConditionEvaluationResult;
030import org.junit.jupiter.api.extension.ExecutionCondition;
031import org.junit.jupiter.api.extension.ExtensionContext;
032import org.junit.platform.commons.support.AnnotationSupport;
033
034/**
035 * {@link ExecutionCondition} for {@link RequirePostgresVersion}.
036 *
037 * @see RequirePostgresVersion
038 * @since 4.1
039 */
040public final class PostgresVersionCondition implements ExecutionCondition {
041
042    @Override
043    public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
044        return AnnotationSupport.findAnnotation(context.getElement(), RequirePostgresVersion.class)
045                .map(this::checkPostgresVersion)
046                .orElse(ConditionEvaluationResult.enabled("No version annotation found"));
047
048    }
049
050    private ConditionEvaluationResult checkPostgresVersion(RequirePostgresVersion requirePostgresVersion) {
051        Version atLeastVersion = Version.valueOf(requirePostgresVersion.atLeast());
052        Version lessThanVersion = Version.valueOf(requirePostgresVersion.lessThan());
053
054        if (atLeastVersion.ignore() && lessThanVersion.ignore()) {
055            return ConditionEvaluationResult.enabled("No PostgreSQL version range set");
056        }
057
058        try (EmbeddedPostgres pg = EmbeddedPostgres.forVersionCheck()) {
059            Version postgresVersion = Version.valueOf(pg.getPostgresVersion());
060
061            if (!atLeastVersion.ignore() && postgresVersion.compareTo(atLeastVersion) < 0) {
062                return ConditionEvaluationResult.disabled(
063                        format("Located PostgreSQL version is %s, at least version %s is required", postgresVersion, atLeastVersion));
064            }
065
066            if (!lessThanVersion.ignore() && lessThanVersion.compareTo(postgresVersion) < 0) {
067                return ConditionEvaluationResult.disabled(
068                        format("Located PostgreSQL version is %s, must be less than %s", postgresVersion, lessThanVersion));
069            }
070
071            return ConditionEvaluationResult.enabled(
072                    format("Located PostgreSQL version is %s, version range is %s - %s", postgresVersion, atLeastVersion, lessThanVersion));
073
074        } catch (IOException e) {
075            return ConditionEvaluationResult.disabled("IOException while checking postgres version", e.getMessage());
076        }
077    }
078
079    @AutoValue
080    abstract static class Version implements Comparable<Version> {
081
082        abstract int major();
083
084        abstract int minor();
085
086        abstract int patch();
087
088        private static Version valueOf(String value) {
089            checkNotNull(value, "value is null");
090
091            List<String> values = Splitter.on('.').trimResults().splitToList(value);
092            return new AutoValue_PostgresVersionCondition_Version(parseValue(values, 0),
093                    parseValue(values, 1),
094                    parseValue(values, 2));
095        }
096
097        private static int parseValue(List<String> values, int pos) {
098            if (values.size() > pos && !values.get(pos).isEmpty()) {
099                try {
100                    return Integer.parseInt(values.get(pos));
101                } catch (NumberFormatException e) {
102                    return 0;
103                }
104            } else {
105                return 0;
106            }
107        }
108
109        private boolean ignore() {
110            return major() == 0 && minor() == 0 && patch() == 0;
111        }
112
113        @Override
114        public int compareTo(Version other) {
115            return ComparisonChain.start()
116                    .compare(major(), other.major())
117                    .compare(minor(), other.minor())
118                    .compare(patch(), other.patch())
119                    .result();
120        }
121
122        @Override
123        public String toString() {
124            if (ignore()) {
125                return "";
126            } else {
127                return Joiner.on('.').join(major(), minor(), patch());
128            }
129        }
130    }
131}