diff --git a/.github/outdatedDependencies.md b/.github/outdatedDependencies.md new file mode 100644 index 00000000000..e08f45ef495 --- /dev/null +++ b/.github/outdatedDependencies.md @@ -0,0 +1,6 @@ +--- +title: Outdated dependencies +labels: code-quality, dependencies +--- +There are outdated dependencies! +See https://github.com/JabRef/jabref/actions?query=is%3Afailure for details. diff --git a/.github/workflows/check-outdated-dependencies.yml b/.github/workflows/check-outdated-dependencies.yml new file mode 100644 index 00000000000..1d3fe5a7750 --- /dev/null +++ b/.github/workflows/check-outdated-dependencies.yml @@ -0,0 +1,29 @@ +name: Check dependencies + +on: + schedule: + - cron: '0 0 * * 0' # Run every Sunday + +jobs: + checkDependencies: + name: Check dependencies + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v1 + with: + depth: 1 + submodules: false + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: 13 + - name: Look for outdated dependencies + run: ./gradlew -q checkOutdatedDependencies + - name: Report issues + if: failure() + uses: JasonEtco/create-an-issue@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + filename: .github/outdatedDependencies.md diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 213d08d4d38..f24fac8c4a6 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -54,6 +54,11 @@ jobs: ${{ runner.OS }}-gradle-${{ env.cache-name }}- ${{ runner.OS }}-gradle- ${{ runner.OS }}- + - uses: actions/cache@v1 + name: Cache gradle wrapper + with: + path: ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} - name: Download jpackage # We need to download jpackage from https://jdk.java.net/jpackage/ run: | diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000000..3d7fc778686 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,236 @@ +name: Tests + +on: [push] + +jobs: + checkstyle: + name: Checkstyle + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v1 + with: + depth: 1 + submodules: false + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: 13 + - uses: actions/cache@v1 + name: Restore gradle chache + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + restore-keys: | + ${{ runner.OS }}-gradle-${{ env.cache-name }}- + ${{ runner.OS }}-gradle- + ${{ runner.OS }}- + - uses: actions/cache@v1 + name: Restore gradle wrapper + with: + path: ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} + - name: Run checkstyle + run: ./gradlew checkstyleMain checkstyleTest checkstyleJmh + tests: + name: Unit tests + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v1 + with: + depth: 1 + submodules: false + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: 13 + - uses: actions/cache@v1 + name: Restore gradle chache + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + restore-keys: | + ${{ runner.OS }}-gradle-${{ env.cache-name }}- + ${{ runner.OS }}-gradle- + ${{ runner.OS }}- + - uses: actions/cache@v1 + name: Restore gradle wrapper + with: + path: ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} + - name: Run tests + run: xvfb-run --auto-servernum ./gradlew check -x checkstyleJmh -x checkstyleMain -x checkstyleTest + env: + CI: "true" + - name: Format failed test results + if: failure() + run: | + sudo apt-get install -qq -y xml-twig-tools xsltproc + scripts/after-failure.sh + databasetests: + name: Database tests + runs-on: ubuntu-latest + services: + postgres: + image: postgres:10.8 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + ports: + - 5432:5432 + # needed because the postgres container does not provide a healthcheck + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + steps: + - name: Checkout source + uses: actions/checkout@v1 + with: + depth: 1 + submodules: false + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: 13 + - uses: actions/cache@v1 + name: Restore gradle chache + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + restore-keys: | + ${{ runner.OS }}-gradle-${{ env.cache-name }}- + ${{ runner.OS }}-gradle- + ${{ runner.OS }}- + - uses: actions/cache@v1 + name: Restore gradle wrapper + with: + path: ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} + - name: Run tests on PostgreSQL + run: ./gradlew databaseTest --rerun-tasks + env: + CI: "true" + DBMS: "postgresql" + - name: Shutdown Ubuntu MySQL + run: sudo service mysql stop # Shutdown the Default MySQL, "sudo" is necessary, please not remove it + - name: Start custom MySQL + uses: mirromutth/mysql-action@v1.1 + with: + host port: 3800 + container port: 3307 + character set server: 'utf8' + collation server: 'utf8_general_ci' + mysql version: '8.0' + mysql database: 'jabref' + mysql root password: 'root' + - name: Run tests on MySQL + run: ./gradlew databaseTest --rerun-tasks + env: + CI: "true" + DBMS: "mysql" + fetchertests: + name: Fetcher tests + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v1 + with: + depth: 1 + submodules: false + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: 13 + - uses: actions/cache@v1 + name: Restore gradle chache + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + restore-keys: | + ${{ runner.OS }}-gradle-${{ env.cache-name }}- + ${{ runner.OS }}-gradle- + ${{ runner.OS }}- + - uses: actions/cache@v1 + name: Restore gradle wrapper + with: + path: ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} + - name: Run fetcher tests + run: ./gradlew fetcherTest + env: + CI: "true" + guitests: + name: GUI tests + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v1 + with: + depth: 1 + submodules: false + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: 13 + - uses: actions/cache@v1 + name: Restore gradle chache + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + restore-keys: | + ${{ runner.OS }}-gradle-${{ env.cache-name }}- + ${{ runner.OS }}-gradle- + ${{ runner.OS }}- + - uses: actions/cache@v1 + name: Restore gradle wrapper + with: + path: ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} + - name: Run GUI tests + run: xvfb-run --auto-servernum ./gradlew guiTest + env: + CI: "true" + codecoverage: + name: Code coverage + runs-on: ubuntu-latest + services: + postgres: + image: postgres:10.8 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + ports: + - 5432:5432 + # needed because the postgres container does not provide a healthcheck + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + steps: + - name: Checkout source + uses: actions/checkout@v1 + with: + depth: 1 + submodules: false + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: 13 + - uses: actions/cache@v1 + name: Restore gradle chache + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + restore-keys: | + ${{ runner.OS }}-gradle-${{ env.cache-name }}- + ${{ runner.OS }}-gradle- + ${{ runner.OS }}- + - uses: actions/cache@v1 + name: Restore gradle wrapper + with: + path: ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} + - name: Update test coverage metrics + run: xvfb-run --auto-servernum ./gradlew jacocoTestReport && bash <(curl -s https://codecov.io/bash); + env: + CI: "false" # we pretend to run locally - even if tests fail on the CI, they count towards test coverage + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + DBMS: "postgresql" diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index dc29ce8976a..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,78 +0,0 @@ -language: java -jdk: - - openjdk13 - -# we test at Ubuntu Trusty (Ubuntu 14.04 LTS) -# see https://docs.travis-ci.com/user/trusty-ci-environment/ -# This environment is continuously updated as described in https://docs.travis-ci.com/user/build-environment-updates/ -dist: trusty -sudo: required - -git: - depth: 1 - -services: - - postgresql - - mysql - -env: - global: - - GRADLE_OPTS=-Dorg.gradle.daemon=false - matrix: - - TEST_SUITE=check - - TEST_SUITE=checkstyle - - TEST_SUITE=fetcherTest - - TEST_SUITE=databaseTest - - TEST_SUITE=guiTest - # codecov may fail by itself (see gradle build file) - # The codecov test itself fails only in case there is an issue with the codecov update. - # Thus, a decrease in the testing coverage does not lead to a failure in Travis - - TEST_SUITE=codecov - - DEPENDENCY_UPDATES=check - -matrix: - fast_finish: true - allow_failures: - - env: TEST_SUITE=fetcherTest - - env: TEST_SUITE=databaseTest - - env: TEST_SUITE=guiTest - - env: DEPENDENCY_UPDATES=check - -# JavaFX localization tests need a running X environment -before_install: - - "export DISPLAY=:99.0" - - "sh -e /etc/init.d/xvfb start" - - sleep 3 # give xvfb some time to start - -install: true - -before_script: - - psql -c 'create database jabref;' -U postgres - - mysql -u root -e 'create database jabref' - -script: - - if [ "$TEST_SUITE" != "guiTest" ] && [ "$TEST_SUITE" != "checkstyle" ] && [ "$TEST_SUITE" != "codecov" ]; then ./gradlew $TEST_SUITE $OPTIONS -x checkstyleJmh -x checkstyleMain -x checkstyleTest; fi - - if [ "$TEST_SUITE" == "checkstyle" ]; then ./gradlew checkstyleMain checkstyleTest checkstyleJmh; fi - - if [ "$TEST_SUITE" == "guiTest" ]; then ./buildres/gui-tests.sh; fi - - if [ "$TEST_SUITE" == "codecov" ]; then ./gradlew jacocoTestReport && bash <(curl -s https://codecov.io/bash); fi - - if [ "$DEPENDENCY_UPDATES" == "check" ]; then ./gradlew -q checkOutdatedDependencies; fi - -after_failure: - # show test results if build fails - - $TRAVIS_BUILD_DIR/scripts/after-failure.sh - -branches: - only: - - master - - maintable-beta - -# cache gradle dependencies -# https://docs.travis-ci.com/user/languages/java#Caching -before_cache: - - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ -cache: - directories: - - $HOME/.gradle/caches/ - - $HOME/.gradle/wrapper/ - diff --git a/build.gradle b/build.gradle index 37529b7ca9b..194c1cd20a3 100644 --- a/build.gradle +++ b/build.gradle @@ -24,6 +24,9 @@ plugins { id 'org.javamodularity.moduleplugin' version '1.5.0' id 'org.openjfx.javafxplugin' version '0.0.8' id 'org.beryx.jlink' version '2.16.4' + + // nicer test outputs during running and completion + id 'com.adarshr.test-logger' version '2.0.0' } gradle.startParameter.showStacktrace = org.gradle.api.logging.configuration.ShowStacktrace.ALWAYS @@ -438,7 +441,6 @@ javadoc { localization.script = 'scripts/syncLang.py' -// Test tasks test { useJUnitPlatform { excludeTags 'DatabaseTest', 'FetcherTest', 'GUITest' @@ -452,6 +454,10 @@ test { } } +testlogger { + showPassed false +} + task databaseTest(type: Test) { useJUnitPlatform { includeTags 'DatabaseTest' @@ -524,8 +530,9 @@ jacocoTestReport { dependsOn jacocoMerge reports { - xml.enabled = true // coveralls plugin depends on xml format report + csv.enabled = true html.enabled = true + xml.enabled = true // coveralls plugin depends on xml format report } } diff --git a/buildres/gui-tests.sh b/buildres/gui-tests.sh deleted file mode 100755 index 137818a1bb3..00000000000 --- a/buildres/gui-tests.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -# no need for databases for the integrationTest -> save memory overflow -# currently does not work: "stop: Unknown instance:" - sudo service mysql stop -sudo service postgresql stop -# following services identified by "sudo service --status-all" do not need to run, too -# excluded: rsyslog (feels wrong), udev (feels wrong), friendly-recovery ("Unknown instance" error) -sudo service acpid stop -sudo service atd stop -sudo service cron stop -sudo service memcached stop -sudo service ntp stop -sudo service rabbitmq-server stop -sudo service resolvconf stop -sudo service sshguard stop -sudo service ssh stop -timeout 60 ./gradlew guiTest -Dscan --info diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 00000000000..8a1e08e5d7b --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,25 @@ +# How to test + +## Database tests + +### PostgreSQL + +To quickly host a local PostgreSQL database, execute following statement: + +```shell command +docker run -d -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=postgres -p 5432:5432 --name db postgres:10 postgres -c log_statement=all +``` + +Set the environment variable `DBMS` to `postgres` (or leave it unset) + +Then, all DBMS Tests (annotated with `@org.jabref.testutils.category.DatabaseTest`) run properly. + +### MySQL + +A MySQL DBMS can be started using following command: + +``´shell command +docker run -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=jabref -p 3800:3307 mysql:8.0 --port=3307 +``` + +Set the environment variable `DBMS` to `mysql`. diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6ce793f21e8..94920145f34 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/scripts/after-failure.sh b/scripts/after-failure.sh index fa029d36898..4770f6c78de 100755 --- a/scripts/after-failure.sh +++ b/scripts/after-failure.sh @@ -1,12 +1,8 @@ #!/bin/bash -# taken from https://github.com/lhotari/travis-gradle-test-failures-to-console/blob/master/travis/junit-errors-to-stdout.sh +# based on https://github.com/lhotari/travis-gradle-test-failures-to-console/blob/master/travis/junit-errors-to-stdout.sh IFS=' ' DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -if [ "$TRAVIS" = "true" ]; then - #echo 'Installing xml-twig-tools and xsltproc....' - sudo apt-get install -qq -y --force-yes xml-twig-tools xsltproc > /dev/null -fi ROOTDIR="$1" if [ -z "$ROOTDIR" ]; then ROOTDIR="." diff --git a/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java b/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java index 78a6f139091..23e96e4d1c8 100644 --- a/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java +++ b/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java @@ -32,6 +32,7 @@ import org.jabref.logic.help.HelpFile; import org.jabref.logic.l10n.Localization; import org.jabref.logic.shared.DBMSConnectionProperties; +import org.jabref.logic.shared.DBMSConnectionPropertiesBuilder; import org.jabref.logic.shared.exception.InvalidDBMSConnectionPropertiesException; import org.jabref.logic.shared.prefs.SharedDatabasePreferences; import org.jabref.logic.shared.security.Password; @@ -109,17 +110,17 @@ public SharedDatabaseLoginDialogViewModel(JabRefFrame frame, DialogService dialo } public boolean openDatabase() { - - DBMSConnectionProperties connectionProperties = new DBMSConnectionProperties(); - connectionProperties.setType(selectedDBMSType.getValue()); - connectionProperties.setHost(host.getValue()); - connectionProperties.setPort(Integer.parseInt(port.getValue())); - connectionProperties.setDatabase(database.getValue()); - connectionProperties.setUser(user.getValue()); - connectionProperties.setPassword(password.getValue()); - connectionProperties.setUseSSL(useSSL.getValue()); - connectionProperties.setKeyStore(keystore.getValue()); - connectionProperties.setServerTimezone(serverTimezone.getValue()); + DBMSConnectionProperties connectionProperties = new DBMSConnectionPropertiesBuilder() + .setType(selectedDBMSType.getValue()) + .setHost(host.getValue()) + .setPort(Integer.parseInt(port.getValue())) + .setDatabase(database.getValue()) + .setUser(user.getValue()) + .setPassword(password.getValue()) + .setUseSSL(useSSL.getValue()) + .setKeyStore(keystore.getValue()) + .setServerTimezone(serverTimezone.getValue()) + .createDBMSConnectionProperties(); setupKeyStore(); boolean connected = openSharedDatabase(connectionProperties); @@ -136,7 +137,7 @@ private boolean openSharedDatabase(DBMSConnectionProperties connectionProperties if (isSharedDatabaseAlreadyPresent(connectionProperties)) { dialogService.showWarningDialogAndWait(Localization.lang("Shared database connection"), - Localization.lang("You are already connected to a database using entered connection details.")); + Localization.lang("You are already connected to a database using entered connection details.")); return true; } @@ -146,9 +147,9 @@ private boolean openSharedDatabase(DBMSConnectionProperties connectionProperties if (Files.exists(localFilePath) && !Files.isDirectory(localFilePath)) { boolean overwriteFilePressed = dialogService.showConfirmationDialogAndWait(Localization.lang("Existing file"), - Localization.lang("'%0' exists. Overwrite file?", localFilePath.getFileName().toString()), - Localization.lang("Overwrite file"), - Localization.lang("Cancel")); + Localization.lang("'%0' exists. Overwrite file?", localFilePath.getFileName().toString()), + Localization.lang("Overwrite file"), + Localization.lang("Cancel")); if (!overwriteFilePressed) { return true; } @@ -174,22 +175,20 @@ private boolean openSharedDatabase(DBMSConnectionProperties connectionProperties } catch (SQLException | InvalidDBMSConnectionPropertiesException exception) { frame.getDialogService().showErrorDialogAndWait(Localization.lang("Connection error"), exception); - } catch (DatabaseNotSupportedException exception) { ButtonType openHelp = new ButtonType("Open Help", ButtonData.OTHER); Optional result = dialogService.showCustomButtonDialogAndWait(AlertType.INFORMATION, - Localization.lang("Migration help information"), - Localization.lang("Entered database has obsolete structure and is no longer supported.") - + "\n" + - Localization.lang("Click help to learn about the migration of pre-3.6 databases.") - + "\n" + - Localization.lang("However, a new database was created alongside the pre-3.6 one."), - ButtonType.OK, openHelp); + Localization.lang("Migration help information"), + Localization.lang("Entered database has obsolete structure and is no longer supported.") + + "\n" + + Localization.lang("Click help to learn about the migration of pre-3.6 databases.") + + "\n" + + Localization.lang("However, a new database was created alongside the pre-3.6 one."), + ButtonType.OK, openHelp); result.filter(btn -> btn.equals(openHelp)).ifPresent(btn -> HelpAction.openHelpPage(HelpFile.SQL_DATABASE_MIGRATION)); result.filter(btn -> btn.equals(ButtonType.OK)).ifPresent(btn -> openSharedDatabase(connectionProperties)); - } loading.set(false); return false; @@ -266,10 +265,10 @@ private boolean isSharedDatabaseAlreadyPresent(DBMSConnectionProperties connecti public void showSaveDbToFileDialog() { FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(StandardFileType.BIBTEX_DB) - .withDefaultExtension(StandardFileType.BIBTEX_DB) - .withInitialDirectory(Globals.prefs.get(JabRefPreferences.WORKING_DIRECTORY)) - .build(); + .addExtensionFilter(StandardFileType.BIBTEX_DB) + .withDefaultExtension(StandardFileType.BIBTEX_DB) + .withInitialDirectory(Globals.prefs.get(JabRefPreferences.WORKING_DIRECTORY)) + .build(); Optional exportPath = dialogService.showFileSaveDialog(fileDialogConfiguration); exportPath.ifPresent(path -> { folder.setValue(path.toString()); @@ -278,11 +277,11 @@ public void showSaveDbToFileDialog() { public void showOpenKeystoreFileDialog() { FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(FileFilterConverter.ANY_FILE) - .addExtensionFilter(StandardFileType.JAVA_KEYSTORE) - .withDefaultExtension(StandardFileType.JAVA_KEYSTORE) - .withInitialDirectory(Globals.prefs.get(JabRefPreferences.WORKING_DIRECTORY)) - .build(); + .addExtensionFilter(FileFilterConverter.ANY_FILE) + .addExtensionFilter(StandardFileType.JAVA_KEYSTORE) + .withDefaultExtension(StandardFileType.JAVA_KEYSTORE) + .withInitialDirectory(Globals.prefs.get(JabRefPreferences.WORKING_DIRECTORY)) + .build(); Optional keystorePath = dialogService.showFileOpenDialog(fileDialogConfiguration); keystorePath.ifPresent(path -> { keystore.setValue(path.toString()); @@ -369,5 +368,7 @@ public ValidationStatus formValidation() { return formValidator.getValidationStatus(); } - public StringProperty serverTimezoneProperty() { return serverTimezone; } + public StringProperty serverTimezoneProperty() { + return serverTimezone; + } } diff --git a/src/main/java/org/jabref/logic/shared/DBMSConnectionProperties.java b/src/main/java/org/jabref/logic/shared/DBMSConnectionProperties.java index f39eb8a41a4..e6d6a68ff43 100644 --- a/src/main/java/org/jabref/logic/shared/DBMSConnectionProperties.java +++ b/src/main/java/org/jabref/logic/shared/DBMSConnectionProperties.java @@ -27,22 +27,50 @@ public class DBMSConnectionProperties implements DatabaseConnectionProperties { private String database; private String user; private String password; + private boolean allowPublicKeyRetrieval; private boolean useSSL; - private String serverTimezone; + private String serverTimezone = ""; - //Not needed for connection, but stored for future login + // Not needed for connection, but stored for future login private String keyStore; - public DBMSConnectionProperties() { - // no data - } - + /** + * Gets all required data from {@link SharedDatabasePreferences} and sets them if present. + */ public DBMSConnectionProperties(SharedDatabasePreferences prefs) { - setFromPreferences(prefs); + if (prefs.getType().isPresent()) { + Optional dbmsType = DBMSType.fromString(prefs.getType().get()); + if (dbmsType.isPresent()) { + this.type = dbmsType.get(); + } + } + + prefs.getHost().ifPresent(theHost -> this.host = theHost); + prefs.getPort().ifPresent(thePort -> this.port = Integer.parseInt(thePort)); + prefs.getName().ifPresent(theDatabase -> this.database = theDatabase); + prefs.getKeyStoreFile().ifPresent(theKeystore -> this.keyStore = theKeystore); + prefs.getServerTimezone().ifPresent(theServerTimezone -> this.serverTimezone = theServerTimezone); + this.useSSL = prefs.isUseSSL(); + + if (prefs.getUser().isPresent()) { + this.user = prefs.getUser().get(); + if (prefs.getPassword().isPresent()) { + try { + this.password = new Password(prefs.getPassword().get().toCharArray(), prefs.getUser().get()).decrypt(); + } catch (UnsupportedEncodingException | GeneralSecurityException e) { + LOGGER.error("Could not decrypt password", e); + } + } + } + + if (!prefs.getPassword().isPresent()) { + // Some DBMS require a non-null value as a password (in case of using an empty string). + this.password = ""; + } } - public DBMSConnectionProperties(DBMSType type, String host, int port, String database, String user, - String password, boolean useSSL, String serverTimezone) { + DBMSConnectionProperties(DBMSType type, String host, int port, String database, String user, + String password, boolean useSSL, boolean allowPublicKeyRetrieval, String serverTimezone, String keyStore) { this.type = type; this.host = host; this.port = port; @@ -50,7 +78,9 @@ public DBMSConnectionProperties(DBMSType type, String host, int port, String dat this.user = user; this.password = password; this.useSSL = useSSL; + this.allowPublicKeyRetrieval = allowPublicKeyRetrieval; this.serverTimezone = serverTimezone; + this.keyStore = keyStore; } @Override @@ -58,75 +88,54 @@ public DBMSType getType() { return type; } - public void setType(DBMSType type) { - this.type = type; - } - @Override public String getHost() { return host; } - public void setHost(String host) { - this.host = host; - } - @Override public int getPort() { return port; } - public void setPort(int port) { - this.port = port; - } - @Override public String getDatabase() { return database; } - public void setDatabase(String database) { - this.database = database; - } - @Override public String getUser() { return user; } - public void setUser(String user) { - this.user = user; - } - @Override public String getPassword() { return password; } - public void setPassword(String password) { - this.password = password; - } - @Override public boolean isUseSSL() { return useSSL; } - public void setUseSSL(boolean useSSL) { - this.useSSL = useSSL; - } - - public String getUrl() { - return type.getUrl(host, port, database); + @Override + public boolean isAllowPublicKeyRetrieval() { + return allowPublicKeyRetrieval; } @Override - public String getServerTimezone() { return serverTimezone; } + public String getServerTimezone() { + return serverTimezone; + } - public void setServerTimezone(String serverTimezone) { this.serverTimezone = serverTimezone; } + public String getUrl() { + String url = type.getUrl(host, port, database); + return url; + } /** * Returns username, password and ssl as Properties Object + * * @return Properties with values for user, password and ssl */ public Properties asProperties() { @@ -134,11 +143,13 @@ public Properties asProperties() { props.setProperty("user", user); props.setProperty("password", password); props.setProperty("serverTimezone", serverTimezone); - if (useSSL) { props.setProperty("ssl", Boolean.toString(useSSL)); + props.setProperty("useSSL", Boolean.toString(useSSL)); + } + if (allowPublicKeyRetrieval) { + props.setProperty("allowPublicKeyRetrieval", Boolean.toString(allowPublicKeyRetrieval)); } - return props; } @@ -147,10 +158,6 @@ public String getKeyStore() { return keyStore; } - public void setKeyStore(String keyStore) { - this.keyStore = keyStore; - } - /** * Compares all properties except the password. */ @@ -165,13 +172,13 @@ public boolean equals(Object obj) { } DBMSConnectionProperties properties = (DBMSConnectionProperties) obj; return Objects.equals(type, properties.getType()) - && this.host.equalsIgnoreCase(properties.getHost()) - && Objects.equals(port, properties.getPort()) - && Objects.equals(database, properties.getDatabase()) - && Objects.equals(user, properties.getUser()) - && Objects.equals(useSSL, properties.isUseSSL()) - && Objects.equals(serverTimezone, properties.getServerTimezone()); - + && this.host.equalsIgnoreCase(properties.getHost()) + && Objects.equals(port, properties.getPort()) + && Objects.equals(database, properties.getDatabase()) + && Objects.equals(user, properties.getUser()) + && Objects.equals(useSSL, properties.isUseSSL()) + && Objects.equals(allowPublicKeyRetrieval, properties.isAllowPublicKeyRetrieval()) + && Objects.equals(serverTimezone, properties.getServerTimezone()); } @Override @@ -179,49 +186,13 @@ public int hashCode() { return Objects.hash(type, host, port, database, user, useSSL); } - /** - * Gets all required data from {@link SharedDatabasePreferences} and sets them if present. - */ - private void setFromPreferences(SharedDatabasePreferences prefs) { - if (prefs.getType().isPresent()) { - Optional dbmsType = DBMSType.fromString(prefs.getType().get()); - if (dbmsType.isPresent()) { - this.type = dbmsType.get(); - } - } - - prefs.getHost().ifPresent(theHost -> this.host = theHost); - prefs.getPort().ifPresent(thePort -> this.port = Integer.parseInt(thePort)); - prefs.getName().ifPresent(theDatabase -> this.database = theDatabase); - prefs.getKeyStoreFile().ifPresent(theKeystore -> this.keyStore = theKeystore); - prefs.getServerTimezone().ifPresent(theServerTimezone -> this.serverTimezone = theServerTimezone); - this.setUseSSL(prefs.isUseSSL()); - - if (prefs.getUser().isPresent()) { - this.user = prefs.getUser().get(); - if (prefs.getPassword().isPresent()) { - try { - this.password = new Password(prefs.getPassword().get().toCharArray(), prefs.getUser().get()).decrypt(); - } catch (UnsupportedEncodingException | GeneralSecurityException e) { - LOGGER.error("Could not decrypt password", e); - } - } - } - - if (!prefs.getPassword().isPresent()) { - // Some DBMS require a non-null value as a password (in case of using an empty string). - this.password = ""; - } - } - @Override public boolean isValid() { return Objects.nonNull(type) - && Objects.nonNull(host) - && Objects.nonNull(port) - && Objects.nonNull(database) - && Objects.nonNull(user) - && Objects.nonNull(password); + && Objects.nonNull(host) + && Objects.nonNull(port) + && Objects.nonNull(database) + && Objects.nonNull(user) + && Objects.nonNull(password); } - } diff --git a/src/main/java/org/jabref/logic/shared/DBMSConnectionPropertiesBuilder.java b/src/main/java/org/jabref/logic/shared/DBMSConnectionPropertiesBuilder.java new file mode 100644 index 00000000000..35e01f6e7f7 --- /dev/null +++ b/src/main/java/org/jabref/logic/shared/DBMSConnectionPropertiesBuilder.java @@ -0,0 +1,73 @@ +package org.jabref.logic.shared; + +import org.jabref.model.database.shared.DBMSType; + +public class DBMSConnectionPropertiesBuilder { + private DBMSType type; + private String host; + private int port = -1; + private String database; + private String user; + private String password; + private boolean useSSL; + private boolean allowPublicKeyRetrieval; + private String serverTimezone = ""; + private String keyStore; + + public DBMSConnectionPropertiesBuilder setType(DBMSType type) { + this.type = type; + return this; + } + + public DBMSConnectionPropertiesBuilder setHost(String host) { + this.host = host; + return this; + } + + public DBMSConnectionPropertiesBuilder setPort(int port) { + this.port = port; + return this; + } + + public DBMSConnectionPropertiesBuilder setDatabase(String database) { + this.database = database; + return this; + } + + public DBMSConnectionPropertiesBuilder setUser(String user) { + this.user = user; + return this; + } + + public DBMSConnectionPropertiesBuilder setPassword(String password) { + this.password = password; + return this; + } + + public DBMSConnectionPropertiesBuilder setUseSSL(boolean useSSL) { + this.useSSL = useSSL; + return this; + } + + public DBMSConnectionPropertiesBuilder setAllowPublicKeyRetrieval(boolean allowPublicKeyRetrieval) { + this.allowPublicKeyRetrieval = allowPublicKeyRetrieval; + return this; + } + + public DBMSConnectionPropertiesBuilder setServerTimezone(String serverTimezone) { + this.serverTimezone = serverTimezone; + return this; + } + + public DBMSConnectionPropertiesBuilder setKeyStore(String keyStore) { + this.keyStore = keyStore; + return this; + } + + public DBMSConnectionProperties createDBMSConnectionProperties() { + if (port == -1) { + port = type.getDefaultPort(); + } + return new DBMSConnectionProperties(type, host, port, database, user, password, useSSL, allowPublicKeyRetrieval, serverTimezone, keyStore); + } +} diff --git a/src/main/java/org/jabref/logic/shared/DBMSProcessor.java b/src/main/java/org/jabref/logic/shared/DBMSProcessor.java index b4651f96eaf..62d8a45a820 100644 --- a/src/main/java/org/jabref/logic/shared/DBMSProcessor.java +++ b/src/main/java/org/jabref/logic/shared/DBMSProcessor.java @@ -66,7 +66,7 @@ public boolean checkBaseIntegrity() throws SQLException { * * @return true if the structure is old, else false. */ - public boolean checkForPare3Dot6Integrity() throws SQLException { + public boolean databaseIsAtMostJabRef35() throws SQLException { return checkTableAvailability( "ENTRIES", "ENTRY_GROUP", @@ -439,7 +439,14 @@ public Optional getSharedEntry(int sharedID) { return Optional.empty(); } + /** + * Queries the database for shared entries. Optionally, they are filtered by the given list of sharedIds + * + * @param sharedIDs the list of Ids to filter. If list is empty, then no filter is applied + */ public List getSharedEntries(List sharedIDs) { + Objects.requireNonNull(sharedIDs); + List sharedEntries = new ArrayList<>(); StringBuilder query = new StringBuilder(); diff --git a/src/main/java/org/jabref/logic/shared/DBMSSynchronizer.java b/src/main/java/org/jabref/logic/shared/DBMSSynchronizer.java index 23753cdde40..8e253219eb3 100644 --- a/src/main/java/org/jabref/logic/shared/DBMSSynchronizer.java +++ b/src/main/java/org/jabref/logic/shared/DBMSSynchronizer.java @@ -8,6 +8,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import org.jabref.logic.exporter.BibDatabaseWriter; import org.jabref.logic.exporter.MetaDataSerializer; @@ -146,15 +147,17 @@ public void initializeDatabases() throws DatabaseNotSupportedException { try { if (!dbmsProcessor.checkBaseIntegrity()) { LOGGER.info("Integrity check failed. Fixing..."); - dbmsProcessor.setupSharedDatabase(); // This check should only be performed once on initial database setup. - // Calling dbmsProcessor.setupSharedDatabase() lets dbmsProcessor.checkBaseIntegrity() be true. - if (dbmsProcessor.checkForPare3Dot6Integrity()) { + if (dbmsProcessor.databaseIsAtMostJabRef35()) { throw new DatabaseNotSupportedException(); } + + // Calling dbmsProcessor.setupSharedDatabase() lets dbmsProcessor.checkBaseIntegrity() be true. + dbmsProcessor.setupSharedDatabase(); } } catch (SQLException e) { + LOGGER.error("Could not check intergrity", e); throw new IllegalStateException(e); } @@ -164,8 +167,8 @@ public void initializeDatabases() throws DatabaseNotSupportedException { } /** - * Synchronizes the local database with shared one. Possible update types are removal, update or insert of a {@link - * BibEntry}. + * Synchronizes the local database with shared one. Possible update types are: removal, update, or insert of a + * {@link BibEntry}. */ @Override public void synchronizeLocalDatabase() { @@ -178,13 +181,13 @@ public void synchronizeLocalDatabase() { // remove old entries locally removeNotSharedEntries(localEntries, idVersionMap.keySet()); - List entriesToDrag = new ArrayList<>(); + List entriesToInsertIntoLocalDatabase = new ArrayList<>(); // compare versions and update local entry if needed for (Map.Entry idVersionEntry : idVersionMap.entrySet()) { - boolean match = false; + boolean remoteEntryMatchingOneLocalEntryFound = false; for (BibEntry localEntry : localEntries) { - if (idVersionEntry.getKey() == localEntry.getSharedBibEntryData().getSharedID()) { - match = true; + if (idVersionEntry.getKey().equals(localEntry.getSharedBibEntryData().getSharedID())) { + remoteEntryMatchingOneLocalEntryFound = true; if (idVersionEntry.getValue() > localEntry.getSharedBibEntryData().getVersion()) { Optional sharedEntry = dbmsProcessor.getSharedEntry(idVersionEntry.getKey()); if (sharedEntry.isPresent()) { @@ -193,7 +196,7 @@ public void synchronizeLocalDatabase() { localEntry.getSharedBibEntryData() .setVersion(sharedEntry.get().getSharedBibEntryData().getVersion()); sharedEntry.get().getFieldMap().forEach( - // copy remote values to local entry + // copy remote values to local entry (field, value) -> localEntry.setField(field, value, EntriesEventSource.SHARED) ); @@ -207,13 +210,14 @@ public void synchronizeLocalDatabase() { } } } - if (!match) { - entriesToDrag.add(idVersionEntry.getKey()); + if (!remoteEntryMatchingOneLocalEntryFound) { + entriesToInsertIntoLocalDatabase.add(idVersionEntry.getKey()); } } - for (BibEntry bibEntry : dbmsProcessor.getSharedEntries(entriesToDrag)) { - bibDatabase.insertEntry(bibEntry, EntriesEventSource.SHARED); + if (!entriesToInsertIntoLocalDatabase.isEmpty()) { + // in case entries should be added into the local database, insert them + bibDatabase.insertEntries(dbmsProcessor.getSharedEntries(entriesToInsertIntoLocalDatabase), EntriesEventSource.SHARED); } } @@ -224,23 +228,15 @@ public void synchronizeLocalDatabase() { * @param sharedIDs Set of all IDs which are present on shared database */ private void removeNotSharedEntries(List localEntries, Set sharedIDs) { - List entriesToRemove = new ArrayList<>(); - for (BibEntry localEntry : localEntries) { - boolean match = false; - for (int sharedID : sharedIDs) { - if (localEntry.getSharedBibEntryData().getSharedID() == sharedID) { - match = true; - break; - } - } - if (!match) { - entriesToRemove.add(localEntry); - } - } + List entriesToRemove = + localEntries.stream() + .filter(localEntry -> !sharedIDs.contains(localEntry.getSharedBibEntryData().getSharedID())) + .collect(Collectors.toList()); if (!entriesToRemove.isEmpty()) { eventBus.post(new SharedEntriesNotPresentEvent(entriesToRemove)); + // remove all non-shared entries without triggering listeners + bibDatabase.removeEntries(entriesToRemove, EntriesEventSource.SHARED); } - bibDatabase.removeEntries(entriesToRemove, EntriesEventSource.SHARED); // Should not reach the listeners above. } /** @@ -257,7 +253,7 @@ public void synchronizeSharedEntry(BibEntry bibEntry) { } catch (OfflineLockException exception) { eventBus.post(new UpdateRefusedEvent(bibDatabaseContext, exception.getLocalBibEntry(), exception.getSharedBibEntry())); } catch (SQLException e) { - LOGGER.error("SQL Error: ", e); + LOGGER.error("SQL Error", e); } } diff --git a/src/main/java/org/jabref/model/database/shared/DBMSType.java b/src/main/java/org/jabref/model/database/shared/DBMSType.java index 90d940c2eb8..4a3052f80e1 100644 --- a/src/main/java/org/jabref/model/database/shared/DBMSType.java +++ b/src/main/java/org/jabref/model/database/shared/DBMSType.java @@ -1,6 +1,6 @@ package org.jabref.model.database.shared; -import java.util.Locale; +import java.util.Arrays; import java.util.Optional; /** @@ -51,11 +51,6 @@ public int getDefaultPort() { } public static Optional fromString(String typeName) { - try { - return Optional.of(Enum.valueOf(DBMSType.class, typeName.toUpperCase(Locale.ENGLISH))); - } catch (IllegalArgumentException exception) { - return Optional.empty(); - } + return Arrays.stream(DBMSType.values()).filter(dbmsType -> dbmsType.type.equalsIgnoreCase(typeName)).findAny(); } - } diff --git a/src/main/java/org/jabref/model/database/shared/DatabaseConnectionProperties.java b/src/main/java/org/jabref/model/database/shared/DatabaseConnectionProperties.java index d6966994bf0..e1861f3f6fe 100644 --- a/src/main/java/org/jabref/model/database/shared/DatabaseConnectionProperties.java +++ b/src/main/java/org/jabref/model/database/shared/DatabaseConnectionProperties.java @@ -20,6 +20,8 @@ public interface DatabaseConnectionProperties { boolean isUseSSL(); + boolean isAllowPublicKeyRetrieval(); + String getServerTimezone(); } diff --git a/src/main/java/org/jabref/model/entry/CanonicalBibEntry.java b/src/main/java/org/jabref/model/entry/CanonicalBibEntry.java index a854db41907..1136c253001 100644 --- a/src/main/java/org/jabref/model/entry/CanonicalBibEntry.java +++ b/src/main/java/org/jabref/model/entry/CanonicalBibEntry.java @@ -56,6 +56,9 @@ public static String getCanonicalRepresentation(BibEntry entry) { String line = String.format(" %s = {%s}", fieldName, String.valueOf(mapFieldToValue.get(fieldName)).replaceAll("\\r\\n", "\n")); sj.add(line); } + + sj.add(String.format(" _jabref_shared = {sharedId: %d, version: %d}", entry.getSharedBibEntryData().getSharedID(), entry.getSharedBibEntryData().getVersion())); + sb.append(sj); // append the closing entry bracket diff --git a/src/main/java/org/jabref/model/entry/SharedBibEntryData.java b/src/main/java/org/jabref/model/entry/SharedBibEntryData.java index 25672e829d8..79546fc202c 100644 --- a/src/main/java/org/jabref/model/entry/SharedBibEntryData.java +++ b/src/main/java/org/jabref/model/entry/SharedBibEntryData.java @@ -1,7 +1,7 @@ package org.jabref.model.entry; /** - * Stores all informations needed to manage entries on a shared (SQL) database. + * Stores all information needed to manage entries on a shared (SQL) database. */ public class SharedBibEntryData { diff --git a/src/test/java/org/jabref/logic/shared/DBMSConnectionPropertiesTest.java b/src/test/java/org/jabref/logic/shared/DBMSConnectionPropertiesTest.java new file mode 100644 index 00000000000..8842284082d --- /dev/null +++ b/src/test/java/org/jabref/logic/shared/DBMSConnectionPropertiesTest.java @@ -0,0 +1,16 @@ +package org.jabref.logic.shared; + +import org.jabref.model.database.shared.DBMSType; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class DBMSConnectionPropertiesTest { + + @Test + void urlForMySqlIncludesSslConfig() { + DBMSConnectionProperties connectionProperties = new DBMSConnectionPropertiesBuilder().setType(DBMSType.MYSQL).setHost("localhost").setPort(3108).setDatabase("jabref").setUser("user").setPassword("password").setUseSSL(false).setAllowPublicKeyRetrieval(true).setServerTimezone("").createDBMSConnectionProperties(); + assertEquals("jdbc:mariadb://localhost:3108/jabref", connectionProperties.getUrl()); + } +} diff --git a/src/test/java/org/jabref/logic/shared/DBMSConnectionTest.java b/src/test/java/org/jabref/logic/shared/DBMSConnectionTest.java index e0cb37bae26..c272d604e7f 100644 --- a/src/test/java/org/jabref/logic/shared/DBMSConnectionTest.java +++ b/src/test/java/org/jabref/logic/shared/DBMSConnectionTest.java @@ -2,14 +2,12 @@ import java.sql.SQLException; -import org.jabref.logic.shared.exception.InvalidDBMSConnectionPropertiesException; import org.jabref.model.database.shared.DBMSType; import org.jabref.testutils.category.DatabaseTest; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; @DatabaseTest @@ -17,15 +15,8 @@ public class DBMSConnectionTest { @ParameterizedTest @EnumSource(DBMSType.class) - public void testGetConnection(DBMSType dbmsType) throws SQLException, InvalidDBMSConnectionPropertiesException { - DBMSConnectionProperties properties = TestConnector.getTestConnectionProperties(dbmsType); - assertNotNull(new DBMSConnection(properties).getConnection()); - } - - @ParameterizedTest - @EnumSource(DBMSType.class) - public void testGetConnectionFail(DBMSType dbmsType) throws SQLException, InvalidDBMSConnectionPropertiesException { + public void getConnectionFailsWhenconnectingToInvalidHost(DBMSType dbmsType) { assertThrows(SQLException.class, - () -> new DBMSConnection(new DBMSConnectionProperties(dbmsType, "XXXX", 0, "XXXX", "XXXX", "XXXX", false, "XXXX")).getConnection()); + () -> new DBMSConnection(new DBMSConnectionPropertiesBuilder().setType(dbmsType).setHost("XXXX").setPort(33778).setDatabase("XXXX").setUser("XXXX").setPassword("XXXX").setUseSSL(false).setServerTimezone("XXXX").createDBMSConnectionProperties()).getConnection()); } } diff --git a/src/test/java/org/jabref/logic/shared/DBMSProcessorTest.java b/src/test/java/org/jabref/logic/shared/DBMSProcessorTest.java index 401ba599020..1aae2ccb1dc 100644 --- a/src/test/java/org/jabref/logic/shared/DBMSProcessorTest.java +++ b/src/test/java/org/jabref/logic/shared/DBMSProcessorTest.java @@ -19,8 +19,11 @@ import org.jabref.model.entry.types.StandardEntryType; import org.jabref.testutils.category.DatabaseTest; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -29,34 +32,40 @@ import static org.junit.jupiter.api.Assertions.fail; @DatabaseTest +@Execution(ExecutionMode.SAME_THREAD) class DBMSProcessorTest { - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - void testCheckBaseIntegrity(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws SQLException { + private DBMSConnection dbmsConnection; + private DBMSProcessor dbmsProcessor; + private DBMSType dbmsType; + + @BeforeEach + public void setup() throws Exception { + this.dbmsType = TestManager.getDBMSTypeTestParameter(); + this.dbmsConnection = TestConnector.getTestDBMSConnection(dbmsType); + this.dbmsProcessor = DBMSProcessor.getProcessorInstance(TestConnector.getTestDBMSConnection(dbmsType)); + TestManager.clearTables(this.dbmsConnection); dbmsProcessor.setupSharedDatabase(); - assertTrue(dbmsProcessor.checkBaseIntegrity()); - clear(dbmsConnection); - assertFalse(dbmsProcessor.checkBaseIntegrity()); + } - clear(dbmsConnection); + @AfterEach + public void closeDbmsConnection() throws SQLException { + this.dbmsConnection.getConnection().close(); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - void testSetUpSharedDatabase(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws SQLException { - dbmsProcessor.setupSharedDatabase(); - clear(dbmsConnection); - dbmsProcessor.setupSharedDatabase(); + @Test + void databaseIntegrityFullFiledAfterSetup() throws SQLException { assertTrue(dbmsProcessor.checkBaseIntegrity()); + } - clear(dbmsConnection); + @Test + void databaseIntegrityBrokenAfterClearedTables() throws SQLException { + TestManager.clearTables(this.dbmsConnection); + assertFalse(dbmsProcessor.checkBaseIntegrity()); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - void testInsertEntry(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws SQLException { - dbmsProcessor.setupSharedDatabase(); + @Test + void testInsertEntry() throws SQLException { BibEntry expectedEntry = getBibEntryExample(); dbmsProcessor.insertEntry(expectedEntry); @@ -84,13 +93,10 @@ void testInsertEntry(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProce Map expectedFieldMap = expectedEntry.getFieldMap().entrySet().stream().collect(Collectors.toMap((entry) -> entry.getKey().getName(), Map.Entry::getValue)); assertEquals(expectedFieldMap, actualFieldMap); - clear(dbmsConnection); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - void testUpdateEntry(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws Exception { - dbmsProcessor.setupSharedDatabase(); + @Test + void testUpdateEntry() throws Exception { BibEntry expectedEntry = getBibEntryExample(); dbmsProcessor.insertEntry(expectedEntry); @@ -101,15 +107,11 @@ void testUpdateEntry(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProce dbmsProcessor.updateEntry(expectedEntry); Optional actualEntry = dbmsProcessor.getSharedEntry(expectedEntry.getSharedBibEntryData().getSharedID()); - assertEquals(expectedEntry, actualEntry.get()); - - clear(dbmsConnection); + assertEquals(Optional.of(expectedEntry), actualEntry); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - void testGetEntriesByIdList(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws Exception { - dbmsProcessor.setupSharedDatabase(); + @Test + void testGetEntriesByIdList() throws Exception { BibEntry firstEntry = getBibEntryExample(); firstEntry.setField(InternalField.INTERNAL_ID_FIELD, "00001"); BibEntry secondEntry = getBibEntryExample(); @@ -120,17 +122,11 @@ void testGetEntriesByIdList(DBMSType dbmsType, DBMSConnection dbmsConnection, DB List sharedEntriesByIdList = dbmsProcessor.getSharedEntries(Arrays.asList(1, 2)); - clear(dbmsConnection); - - assertEquals(firstEntry, sharedEntriesByIdList.get(0)); - assertEquals(secondEntry, sharedEntriesByIdList.get(1)); - + assertEquals(List.of(firstEntry, secondEntry), sharedEntriesByIdList); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - void testUpdateNewerEntry(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws OfflineLockException, SQLException { - dbmsProcessor.setupSharedDatabase(); + @Test + void testUpdateNewerEntry() { BibEntry bibEntry = getBibEntryExample(); dbmsProcessor.insertEntry(bibEntry); @@ -140,14 +136,10 @@ void testUpdateNewerEntry(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMS bibEntry.setField(StandardField.YEAR, "1993"); assertThrows(OfflineLockException.class, () -> dbmsProcessor.updateEntry(bibEntry)); - - clear(dbmsConnection); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - void testUpdateEqualEntry(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws OfflineLockException, SQLException { - dbmsProcessor.setupSharedDatabase(); + @Test + void testUpdateEqualEntry() throws OfflineLockException, SQLException { BibEntry expectedBibEntry = getBibEntryExample(); dbmsProcessor.insertEntry(expectedBibEntry); @@ -156,17 +148,13 @@ void testUpdateEqualEntry(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMS dbmsProcessor.updateEntry(expectedBibEntry); Optional actualBibEntryOptional = dbmsProcessor - .getSharedEntry(expectedBibEntry.getSharedBibEntryData().getSharedID()); + .getSharedEntry(expectedBibEntry.getSharedBibEntryData().getSharedID()); - assertEquals(expectedBibEntry, actualBibEntryOptional.get()); - - clear(dbmsConnection); + assertEquals(Optional.of(expectedBibEntry), actualBibEntryOptional); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - void testRemoveAllEntries(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws SQLException { - dbmsProcessor.setupSharedDatabase(); + @Test + void testRemoveAllEntries() throws SQLException { BibEntry firstEntry = getBibEntryExample(); BibEntry secondEntry = getBibEntryExample(); List entriesToRemove = Arrays.asList(firstEntry, secondEntry); @@ -177,13 +165,10 @@ void testRemoveAllEntries(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMS try (ResultSet resultSet = selectFrom("ENTRY", dbmsConnection, dbmsProcessor)) { assertFalse(resultSet.next()); } - clear(dbmsConnection); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - void testRemoveSomeEntries(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws SQLException { - dbmsProcessor.setupSharedDatabase(); + @Test + void testRemoveSomeEntries() throws SQLException { BibEntry firstEntry = getBibEntryExample(); BibEntry secondEntry = getBibEntryExample(); BibEntry thirdEntry = getBibEntryExample(); @@ -201,14 +186,10 @@ void testRemoveSomeEntries(DBMSType dbmsType, DBMSConnection dbmsConnection, DBM assertEquals(2, entryResultSet.getInt("SHARED_ID")); assertFalse(entryResultSet.next()); } - - clear(dbmsConnection); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - void testRemoveSingleEntry(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws SQLException { - dbmsProcessor.setupSharedDatabase(); + @Test + void testRemoveSingleEntry() throws SQLException { BibEntry entryToRemove = getBibEntryExample(); dbmsProcessor.insertEntry(entryToRemove); dbmsProcessor.removeEntries(Collections.singletonList(entryToRemove)); @@ -216,74 +197,52 @@ void testRemoveSingleEntry(DBMSType dbmsType, DBMSConnection dbmsConnection, DBM try (ResultSet entryResultSet = selectFrom("ENTRY", dbmsConnection, dbmsProcessor)) { assertFalse(entryResultSet.next()); } - - clear(dbmsConnection); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - void testRemoveEntriesOnNullThrows(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws SQLException { - dbmsProcessor.setupSharedDatabase(); + @Test + void testRemoveEntriesOnNullThrows() { assertThrows(NullPointerException.class, () -> dbmsProcessor.removeEntries(null)); - clear(dbmsConnection); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - void testRemoveEmptyEntryList(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws SQLException { - dbmsProcessor.setupSharedDatabase(); + @Test + void testRemoveEmptyEntryList() throws SQLException { dbmsProcessor.removeEntries(Collections.emptyList()); try (ResultSet entryResultSet = selectFrom("ENTRY", dbmsConnection, dbmsProcessor)) { assertFalse(entryResultSet.next()); } - - clear(dbmsConnection); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - void testGetSharedEntries(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws SQLException { - dbmsProcessor.setupSharedDatabase(); + @Test + void testGetSharedEntries() { BibEntry bibEntry = getBibEntryExampleWithEmptyFields(); dbmsProcessor.insertEntry(bibEntry); - List expectedEntries = Arrays.asList(bibEntry); List actualEntries = dbmsProcessor.getSharedEntries(); - assertEquals(expectedEntries, actualEntries); - clear(dbmsConnection); + assertEquals(List.of(bibEntry), actualEntries); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - void testGetSharedEntry(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws SQLException { - dbmsProcessor.setupSharedDatabase(); + @Test + void testGetSharedEntry() { BibEntry expectedBibEntry = getBibEntryExampleWithEmptyFields(); dbmsProcessor.insertEntry(expectedBibEntry); Optional actualBibEntryOptional = dbmsProcessor.getSharedEntry(expectedBibEntry.getSharedBibEntryData().getSharedID()); - assertEquals(expectedBibEntry, actualBibEntryOptional.get()); - clear(dbmsConnection); + assertEquals(Optional.of(expectedBibEntry), actualBibEntryOptional); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - void testGetNotExistingSharedEntry(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws SQLException { - dbmsProcessor.setupSharedDatabase(); + @Test + void testGetNotExistingSharedEntry() { Optional actualBibEntryOptional = dbmsProcessor.getSharedEntry(1); assertFalse(actualBibEntryOptional.isPresent()); - - clear(dbmsConnection); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - void testGetSharedIDVersionMapping(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws OfflineLockException, SQLException { - dbmsProcessor.setupSharedDatabase(); + @Test + void testGetSharedIDVersionMapping() throws OfflineLockException, SQLException { BibEntry firstEntry = getBibEntryExample(); BibEntry secondEntry = getBibEntryExample(); @@ -298,14 +257,10 @@ void testGetSharedIDVersionMapping(DBMSType dbmsType, DBMSConnection dbmsConnect Map actualIDVersionMap = dbmsProcessor.getSharedIDVersionMapping(); assertEquals(expectedIDVersionMap, actualIDVersionMap); - - clear(dbmsConnection); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - void testGetSharedMetaData(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws SQLException { - dbmsProcessor.setupSharedDatabase(); + @Test + void testGetSharedMetaData() { insertMetaData("databaseType", "bibtex;", dbmsConnection, dbmsProcessor); insertMetaData("protectedFlag", "true;", dbmsConnection, dbmsProcessor); insertMetaData("saveActions", "enabled;\nauthor[capitalize,html_to_latex]\ntitle[title_case]\n;", dbmsConnection, dbmsProcessor); @@ -315,23 +270,19 @@ void testGetSharedMetaData(DBMSType dbmsType, DBMSConnection dbmsConnection, DBM Map actualMetaData = dbmsProcessor.getSharedMetaData(); assertEquals(expectedMetaData, actualMetaData); - clear(dbmsConnection); } - @ParameterizedTest - @MethodSource("getTestingDatabaseSystems") - void testSetSharedMetaData(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws SQLException { - dbmsProcessor.setupSharedDatabase(); + @Test + void testSetSharedMetaData() throws SQLException { Map expectedMetaData = getMetaDataExample(); dbmsProcessor.setSharedMetaData(expectedMetaData); Map actualMetaData = dbmsProcessor.getSharedMetaData(); assertEquals(expectedMetaData, actualMetaData); - clear(dbmsConnection); } - private Map getMetaDataExample() { + private static Map getMetaDataExample() { Map expectedMetaData = new HashMap<>(); expectedMetaData.put("databaseType", "bibtex;"); @@ -342,23 +293,22 @@ private Map getMetaDataExample() { return expectedMetaData; } - private BibEntry getBibEntryExampleWithEmptyFields() { - BibEntry bibEntry = new BibEntry(); - bibEntry.setField(StandardField.AUTHOR, "Author"); - bibEntry.setField(StandardField.TITLE, ""); - bibEntry.setField(StandardField.YEAR, ""); + private static BibEntry getBibEntryExampleWithEmptyFields() { + BibEntry bibEntry = new BibEntry() + .withField(StandardField.AUTHOR, "Author") + .withField(StandardField.TITLE, "") + .withField(StandardField.YEAR, ""); bibEntry.getSharedBibEntryData().setSharedID(1); return bibEntry; } - private BibEntry getBibEntryExample() { - BibEntry bibEntry = new BibEntry(StandardEntryType.InProceedings); - bibEntry.setField(StandardField.AUTHOR, "Wirthlin, Michael J and Hutchings, Brad L and Gilson, Kent L"); - bibEntry.setField(StandardField.TITLE, "The nano processor: a low resource reconfigurable processor"); - bibEntry.setField(StandardField.BOOKTITLE, "FPGAs for Custom Computing Machines, 1994. Proceedings. IEEE Workshop on"); - bibEntry.setField(StandardField.YEAR, "1994"); - bibEntry.setCiteKey("nanoproc1994"); - return bibEntry; + private static BibEntry getBibEntryExample() { + return new BibEntry(StandardEntryType.InProceedings) + .withField(StandardField.AUTHOR, "Wirthlin, Michael J and Hutchings, Brad L and Gilson, Kent L") + .withField(StandardField.TITLE, "The nano processor: a low resource reconfigurable processor") + .withField(StandardField.BOOKTITLE, "FPGAs for Custom Computing Machines, 1994. Proceedings. IEEE Workshop on") + .withField(StandardField.YEAR, "1994") + .withCiteKey("nanoproc1994"); } private ResultSet selectFrom(String table, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) { @@ -375,22 +325,18 @@ private ResultSet selectFrom(String table, DBMSConnection dbmsConnection, DBMSPr private void insertMetaData(String key, String value, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) { try { dbmsConnection.getConnection().createStatement().executeUpdate("INSERT INTO " + escape("METADATA", dbmsProcessor) + "(" - + escape("KEY", dbmsProcessor) + ", " + escape("VALUE", dbmsProcessor) + ") VALUES(" - + escapeValue(key) + ", " + escapeValue(value) + ")"); + + escape("KEY", dbmsProcessor) + ", " + escape("VALUE", dbmsProcessor) + ") VALUES(" + + escapeValue(key) + ", " + escapeValue(value) + ")"); } catch (SQLException e) { fail(e.getMessage()); } } - private String escape(String expression, DBMSProcessor dbmsProcessor) { + private static String escape(String expression, DBMSProcessor dbmsProcessor) { return dbmsProcessor.escape(expression); } - private String escapeValue(String value) { + private static String escapeValue(String value) { return "'" + value + "'"; } - - void clear(DBMSConnection dbmsConnection) throws SQLException { - TestManager.clearTables(dbmsConnection); - } } diff --git a/src/test/java/org/jabref/logic/shared/DBMSSynchronizerTest.java b/src/test/java/org/jabref/logic/shared/DBMSSynchronizerTest.java index 4d41a5a15d9..7c0cbba838f 100644 --- a/src/test/java/org/jabref/logic/shared/DBMSSynchronizerTest.java +++ b/src/test/java/org/jabref/logic/shared/DBMSSynchronizerTest.java @@ -24,38 +24,59 @@ import org.jabref.model.util.DummyFileUpdateMonitor; import org.jabref.testutils.category.DatabaseTest; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @DatabaseTest +@Execution(ExecutionMode.SAME_THREAD) public class DBMSSynchronizerTest { private DBMSSynchronizer dbmsSynchronizer; private BibDatabase bibDatabase; private final GlobalBibtexKeyPattern pattern = GlobalBibtexKeyPattern.fromPattern("[auth][year]"); + private DBMSConnection dbmsConnection; + private DBMSProcessor dbmsProcessor; + private DBMSType dbmsType; + + private BibEntry createExampleBibEntry(int index) { + BibEntry bibEntry = new BibEntry(StandardEntryType.Book) + .withField(StandardField.AUTHOR, "Wirthlin, Michael J" + index) + .withField(StandardField.TITLE, "The nano processor" + index); + bibEntry.getSharedBibEntryData().setSharedID(index); + return bibEntry; + } - public void setUp(DBMSConnection dbmsConnection) throws Exception { - clear(dbmsConnection); + @BeforeEach + public void setup() throws Exception { + this.dbmsType = TestManager.getDBMSTypeTestParameter(); + this.dbmsConnection = TestConnector.getTestDBMSConnection(dbmsType); + this.dbmsProcessor = DBMSProcessor.getProcessorInstance(this.dbmsConnection); + TestManager.clearTables(this.dbmsConnection); bibDatabase = new BibDatabase(); BibDatabaseContext context = new BibDatabaseContext(bibDatabase); - dbmsSynchronizer = new DBMSSynchronizer(context, ',', pattern, new DummyFileUpdateMonitor()); + dbmsSynchronizer = new DBMSSynchronizer(context, ',', pattern, new DummyFileUpdateMonitor()); bibDatabase.registerListener(dbmsSynchronizer); dbmsSynchronizer.openSharedDatabase(dbmsConnection); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - public void testEntryAddedEventListener(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws Exception { - setUp(dbmsConnection); + @AfterEach + public void clear() throws SQLException { + this.dbmsConnection.getConnection().close(); + } - BibEntry expectedEntry = getBibEntryExample(1); - BibEntry furtherEntry = getBibEntryExample(1); + @Test + public void testEntryAddedEventListener() throws Exception { + BibEntry expectedEntry = createExampleBibEntry(1); + BibEntry furtherEntry = createExampleBibEntry(1); bibDatabase.insertEntry(expectedEntry); // should not add into shared database. @@ -63,36 +84,43 @@ public void testEntryAddedEventListener(DBMSType dbmsType, DBMSConnection dbmsCo List actualEntries = dbmsProcessor.getSharedEntries(); - assertEquals(1, actualEntries.size()); - assertEquals(expectedEntry, actualEntries.get(0)); - - clear(dbmsConnection); + assertEquals(List.of(expectedEntry), actualEntries); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - public void testFieldChangedEventListener(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws Exception { - setUp(dbmsConnection); - - BibEntry expectedEntry = getBibEntryExample(1); + @Test + public void twoLocalFieldChangesAreSynchronizedCorrectly() throws Exception { + BibEntry expectedEntry = createExampleBibEntry(1); expectedEntry.registerListener(dbmsSynchronizer); bibDatabase.insertEntry(expectedEntry); expectedEntry.setField(StandardField.AUTHOR, "Brad L and Gilson"); - expectedEntry.setField(StandardField.TITLE, "The micro multiplexer", EntriesEventSource.SHARED); + expectedEntry.setField(StandardField.TITLE, "The micro multiplexer"); List actualEntries = dbmsProcessor.getSharedEntries(); assertEquals(Collections.singletonList(expectedEntry), actualEntries); - - clear(dbmsConnection); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - public void testEntriesRemovedEventListener(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws Exception { - setUp(dbmsConnection); + @Test + public void oneLocalAndOneSharedFieldChangeIsSynchronizedCorrectly() throws Exception { + BibEntry exampleBibEntry = createExampleBibEntry(1); + exampleBibEntry.registerListener(dbmsSynchronizer); - BibEntry bibEntry = getBibEntryExample(1); + bibDatabase.insertEntry(exampleBibEntry); + exampleBibEntry.setField(StandardField.AUTHOR, "Brad L and Gilson"); + // shared updates are not synchronized back to the remote database + exampleBibEntry.setField(StandardField.TITLE, "The micro multiplexer", EntriesEventSource.SHARED); + + List actualEntries = dbmsProcessor.getSharedEntries(); + + BibEntry expectedBibEntry = createExampleBibEntry(1) + .withField(StandardField.AUTHOR, "Brad L and Gilson"); + + assertEquals(Collections.singletonList(expectedBibEntry), actualEntries); + } + + @Test + public void testEntriesRemovedEventListener() throws Exception { + BibEntry bibEntry = createExampleBibEntry(1); bibDatabase.insertEntry(bibEntry); List actualEntries = dbmsProcessor.getSharedEntries(); @@ -110,15 +138,10 @@ public void testEntriesRemovedEventListener(DBMSType dbmsType, DBMSConnection db actualEntries = dbmsProcessor.getSharedEntries(); assertEquals(1, actualEntries.size()); assertEquals(bibEntry, actualEntries.get(0)); - - clear(dbmsConnection); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - public void testMetaDataChangedEventListener(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws Exception { - setUp(dbmsConnection); - + @Test + public void testMetaDataChangedEventListener() throws Exception { MetaData testMetaData = new MetaData(); testMetaData.registerListener(dbmsSynchronizer); dbmsSynchronizer.setMetaData(testMetaData); @@ -128,29 +151,19 @@ public void testMetaDataChangedEventListener(DBMSType dbmsType, DBMSConnection d Map actualMap = dbmsProcessor.getSharedMetaData(); assertEquals(expectedMap, actualMap); - - clear(dbmsConnection); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - public void testInitializeDatabases(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws Exception { - setUp(dbmsConnection); - - clear(dbmsConnection); + @Test + public void testInitializeDatabases() throws Exception { dbmsSynchronizer.initializeDatabases(); assertTrue(dbmsProcessor.checkBaseIntegrity()); dbmsSynchronizer.initializeDatabases(); assertTrue(dbmsProcessor.checkBaseIntegrity()); - clear(dbmsConnection); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - public void testSynchronizeLocalDatabaseWithEntryRemoval(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws Exception { - setUp(dbmsConnection); - - List expectedBibEntries = Arrays.asList(getBibEntryExample(1), getBibEntryExample(2)); + @Test + public void testSynchronizeLocalDatabaseWithEntryRemoval() throws Exception { + List expectedBibEntries = Arrays.asList(createExampleBibEntry(1), createExampleBibEntry(2)); dbmsProcessor.insertEntry(expectedBibEntries.get(0)); dbmsProcessor.insertEntry(expectedBibEntries.get(1)); @@ -168,38 +181,47 @@ public void testSynchronizeLocalDatabaseWithEntryRemoval(DBMSType dbmsType, DBMS dbmsSynchronizer.synchronizeLocalDatabase(); assertEquals(expectedBibEntries, bibDatabase.getEntries()); - - clear(dbmsConnection); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - public void testSynchronizeLocalDatabaseWithEntryUpdate(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws Exception { - setUp(dbmsConnection); - - BibEntry bibEntry = getBibEntryExample(1); + @Test + public void testSynchronizeLocalDatabaseWithEntryUpdate() throws Exception { + BibEntry bibEntry = createExampleBibEntry(1); bibDatabase.insertEntry(bibEntry); - assertEquals(1, bibDatabase.getEntries().size()); + assertEquals(List.of(bibEntry), bibDatabase.getEntries()); - BibEntry modifiedBibEntry = getBibEntryExample(1); - modifiedBibEntry.setField(new UnknownField("custom"), "custom value"); + BibEntry modifiedBibEntry = createExampleBibEntry(1) + .withField(new UnknownField("custom"), "custom value"); modifiedBibEntry.clearField(StandardField.TITLE); modifiedBibEntry.setType(StandardEntryType.Article); dbmsProcessor.updateEntry(modifiedBibEntry); - //testing point + assertEquals(1, modifiedBibEntry.getSharedBibEntryData().getSharedID()); dbmsSynchronizer.synchronizeLocalDatabase(); - assertEquals(bibDatabase.getEntries(), dbmsProcessor.getSharedEntries()); - clear(dbmsConnection); + assertEquals(List.of(modifiedBibEntry), bibDatabase.getEntries()); + assertEquals(List.of(modifiedBibEntry), dbmsProcessor.getSharedEntries()); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - public void testApplyMetaData(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws Exception { - setUp(dbmsConnection); + @Test + public void updateEntryDoesNotModifyLocalDatabase() throws Exception { + BibEntry bibEntry = createExampleBibEntry(1); + bibDatabase.insertEntry(bibEntry); + assertEquals(List.of(bibEntry), bibDatabase.getEntries()); + + BibEntry modifiedBibEntry = createExampleBibEntry(1) + .withField(new UnknownField("custom"), "custom value"); + modifiedBibEntry.clearField(StandardField.TITLE); + modifiedBibEntry.setType(StandardEntryType.Article); + + dbmsProcessor.updateEntry(modifiedBibEntry); - BibEntry bibEntry = getBibEntryExample(1); + assertEquals(List.of(bibEntry), bibDatabase.getEntries()); + assertEquals(List.of(modifiedBibEntry), dbmsProcessor.getSharedEntries()); + } + + @Test + public void testApplyMetaData() throws Exception { + BibEntry bibEntry = createExampleBibEntry(1); bibDatabase.insertEntry(bibEntry); MetaData testMetaData = new MetaData(); @@ -209,20 +231,5 @@ public void testApplyMetaData(DBMSType dbmsType, DBMSConnection dbmsConnection, dbmsSynchronizer.applyMetaData(); assertEquals("wirthlin, michael j1", bibEntry.getField(StandardField.AUTHOR).get()); - - clear(dbmsConnection); - } - - private BibEntry getBibEntryExample(int index) { - BibEntry bibEntry = new BibEntry(); - bibEntry.setType(StandardEntryType.Book); - bibEntry.setField(StandardField.AUTHOR, "Wirthlin, Michael J" + index); - bibEntry.setField(StandardField.TITLE, "The nano processor" + index); - bibEntry.getSharedBibEntryData().setSharedID(index); - return bibEntry; - } - - public void clear(DBMSConnection dbmsConnection) throws SQLException { - TestManager.clearTables(dbmsConnection); } } diff --git a/src/test/java/org/jabref/logic/shared/DBMSTypeTest.java b/src/test/java/org/jabref/logic/shared/DBMSTypeTest.java index 87ba1169cc3..94dd5d67dbf 100644 --- a/src/test/java/org/jabref/logic/shared/DBMSTypeTest.java +++ b/src/test/java/org/jabref/logic/shared/DBMSTypeTest.java @@ -1,5 +1,7 @@ package org.jabref.logic.shared; +import java.util.Optional; + import org.jabref.model.database.shared.DBMSType; import org.jabref.testutils.category.DatabaseTest; @@ -18,6 +20,31 @@ public void testToString() { assertEquals("PostgreSQL", DBMSType.POSTGRESQL.toString()); } + @Test + public void fromStringWorksForMySQL() { + assertEquals(Optional.of(DBMSType.MYSQL), DBMSType.fromString("MySQL")); + } + + @Test + public void fromStringWorksForPostgreSQL() { + assertEquals(Optional.of(DBMSType.POSTGRESQL), DBMSType.fromString("PostgreSQL")); + } + + @Test + public void fromStringWorksForNullString() { + assertEquals(Optional.empty(), DBMSType.fromString(null)); + } + + @Test + public void fromStringWorksForEmptyString() { + assertEquals(Optional.empty(), DBMSType.fromString("")); + } + + @Test + public void fromStringWorksForUnkownString() { + assertEquals(Optional.empty(), DBMSType.fromString("unknown")); + } + @Test public void testGetDriverClassPath() { assertEquals("org.mariadb.jdbc.Driver", DBMSType.MYSQL.getDriverClassPath()); diff --git a/src/test/java/org/jabref/logic/shared/SynchronizationTestSimulator.java b/src/test/java/org/jabref/logic/shared/SynchronizationTestSimulator.java index fb8bf59b0d5..6bda1fcb941 100644 --- a/src/test/java/org/jabref/logic/shared/SynchronizationTestSimulator.java +++ b/src/test/java/org/jabref/logic/shared/SynchronizationTestSimulator.java @@ -1,12 +1,12 @@ package org.jabref.logic.shared; import java.sql.SQLException; +import java.util.List; import org.jabref.model.Defaults; import org.jabref.model.bibtexkeypattern.GlobalBibtexKeyPattern; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.BibDatabaseMode; -import org.jabref.model.database.shared.DBMSType; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.field.UnknownField; @@ -14,8 +14,11 @@ import org.jabref.model.util.DummyFileUpdateMonitor; import org.jabref.testutils.category.DatabaseTest; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -24,6 +27,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; @DatabaseTest +@Execution(ExecutionMode.SAME_THREAD) public class SynchronizationTestSimulator { private BibDatabaseContext clientContextA; @@ -31,24 +35,42 @@ public class SynchronizationTestSimulator { private SynchronizationTestEventListener eventListenerB; // used to monitor occurring events private final GlobalBibtexKeyPattern pattern = GlobalBibtexKeyPattern.fromPattern("[auth][year]"); - public void setUp(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws Exception { + private BibEntry getBibEntryExample(int index) { + return new BibEntry(StandardEntryType.InProceedings) + .withField(StandardField.AUTHOR, "Wirthlin, Michael J and Hutchings, Brad L and Gilson, Kent L " + index) + .withField(StandardField.TITLE, "The nano processor: a low resource reconfigurable processor " + index) + .withField(StandardField.BOOKTITLE, "FPGAs for Custom Computing Machines, 1994. Proceedings. IEEE Workshop on " + index) + .withField(StandardField.YEAR, "199" + index) + .withCiteKey("nanoproc199" + index); + } + + @BeforeEach + public void setup() throws Exception { + DBMSConnection dbmsConnection = TestConnector.getTestDBMSConnection(TestManager.getDBMSTypeTestParameter()); + TestManager.clearTables(dbmsConnection); + clientContextA = new BibDatabaseContext(new Defaults(BibDatabaseMode.BIBTEX)); DBMSSynchronizer synchronizerA = new DBMSSynchronizer(clientContextA, ',', pattern, new DummyFileUpdateMonitor()); clientContextA.convertToSharedDatabase(synchronizerA); clientContextA.getDBMSSynchronizer().openSharedDatabase(dbmsConnection); clientContextB = new BibDatabaseContext(new Defaults(BibDatabaseMode.BIBTEX)); - DBMSSynchronizer synchronizerB = new DBMSSynchronizer(clientContextA, ',', pattern, new DummyFileUpdateMonitor()); + DBMSSynchronizer synchronizerB = new DBMSSynchronizer(clientContextB, ',', pattern, new DummyFileUpdateMonitor()); clientContextB.convertToSharedDatabase(synchronizerB); - clientContextB.getDBMSSynchronizer().openSharedDatabase(dbmsConnection); + // use a second connection, because this is another client (typically on another machine) + clientContextB.getDBMSSynchronizer().openSharedDatabase(TestConnector.getTestDBMSConnection(TestManager.getDBMSTypeTestParameter())); eventListenerB = new SynchronizationTestEventListener(); clientContextB.getDBMSSynchronizer().registerListener(eventListenerB); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - public void simulateEntryInsertionAndManualPull(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws Exception { - setUp(dbmsType, dbmsConnection, dbmsProcessor); + @AfterEach + public void clear() throws SQLException { + clientContextA.getDBMSSynchronizer().closeSharedDatabase(); + clientContextB.getDBMSSynchronizer().closeSharedDatabase(); + } + + @Test + public void simulateEntryInsertionAndManualPull() throws Exception { //client A inserts an entry clientContextA.getDatabase().insertEntry(getBibEntryExample(1)); //client A inserts another entry @@ -57,35 +79,25 @@ public void simulateEntryInsertionAndManualPull(DBMSType dbmsType, DBMSConnectio clientContextB.getDBMSSynchronizer().pullChanges(); assertEquals(clientContextA.getDatabase().getEntries(), clientContextB.getDatabase().getEntries()); - - clear(dbmsConnection); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - public void simulateEntryUpdateAndManualPull(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws Exception { - setUp(dbmsType, dbmsConnection, dbmsProcessor); - + @Test + public void simulateEntryUpdateAndManualPull() throws Exception { BibEntry bibEntry = getBibEntryExample(1); - //client A inserts an entry + // client A inserts an entry clientContextA.getDatabase().insertEntry(bibEntry); - //client A changes the entry + // client A changes the entry bibEntry.setField(new UnknownField("custom"), "custom value"); - //client B pulls the changes + // client B pulls the changes bibEntry.clearField(StandardField.AUTHOR); clientContextB.getDBMSSynchronizer().pullChanges(); assertEquals(clientContextA.getDatabase().getEntries(), clientContextB.getDatabase().getEntries()); - - clear(dbmsConnection); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - public void simulateEntryDelitionAndManualPull(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws Exception { - setUp(dbmsType, dbmsConnection, dbmsProcessor); - + @Test + public void simulateEntryDelitionAndManualPull() throws Exception { BibEntry bibEntry = getBibEntryExample(1); //client A inserts an entry clientContextA.getDatabase().insertEntry(bibEntry); @@ -103,15 +115,10 @@ public void simulateEntryDelitionAndManualPull(DBMSType dbmsType, DBMSConnection assertTrue(clientContextA.getDatabase().getEntries().isEmpty()); assertTrue(clientContextB.getDatabase().getEntries().isEmpty()); - - clear(dbmsConnection); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - public void simulateUpdateOnNoLongerExistingEntry(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws Exception { - setUp(dbmsType, dbmsConnection, dbmsProcessor); - + @Test + public void simulateUpdateOnNoLongerExistingEntry() throws Exception { BibEntry bibEntryOfClientA = getBibEntryExample(1); //client A inserts an entry clientContextA.getDatabase().insertEntry(bibEntryOfClientA); @@ -133,53 +140,31 @@ public void simulateUpdateOnNoLongerExistingEntry(DBMSType dbmsType, DBMSConnect // here a new SharedEntryNotPresentEvent has been thrown. In this case the user B would get an pop-up window. assertNotNull(eventListenerB.getSharedEntriesNotPresentEvent()); - assertEquals(bibEntryOfClientB, eventListenerB.getSharedEntriesNotPresentEvent().getBibEntries()); - - clear(dbmsConnection); + assertEquals(List.of(bibEntryOfClientB), eventListenerB.getSharedEntriesNotPresentEvent().getBibEntries()); } - @ParameterizedTest - @MethodSource("org.jabref.logic.shared.TestManager#getTestingDatabaseSystems") - public void simulateEntryChangeConflicts(DBMSType dbmsType, DBMSConnection dbmsConnection, DBMSProcessor dbmsProcessor) throws Exception { - setUp(dbmsType, dbmsConnection, dbmsProcessor); - + @Test + public void simulateEntryChangeConflicts() { BibEntry bibEntryOfClientA = getBibEntryExample(1); - //client A inserts an entry + // client A inserts an entry clientContextA.getDatabase().insertEntry(bibEntryOfClientA); - //client B pulls the entry + // client B pulls the entry clientContextB.getDBMSSynchronizer().pullChanges(); - //A now increases the version number + // A now increases the version number bibEntryOfClientA.setField(StandardField.YEAR, "2001"); // B does nothing here, so there is no event occurrence - // B now tries to update the entry assertFalse(clientContextB.getDatabase().getEntries().isEmpty()); - assertNull(eventListenerB.getUpdateRefusedEvent()); BibEntry bibEntryOfClientB = clientContextB.getDatabase().getEntries().get(0); - //B also tries to change something + // B also tries to change something bibEntryOfClientB.setField(StandardField.YEAR, "2016"); // B now cannot update the shared entry, due to optimistic offline lock. // In this case an BibEntry merge dialog pops up. assertNotNull(eventListenerB.getUpdateRefusedEvent()); - - clear(dbmsConnection); - } - - private BibEntry getBibEntryExample(int index) { - BibEntry bibEntry = new BibEntry(StandardEntryType.InProceedings); - bibEntry.setField(StandardField.AUTHOR, "Wirthlin, Michael J and Hutchings, Brad L and Gilson, Kent L " + index); - bibEntry.setField(StandardField.TITLE, "The nano processor: a low resource reconfigurable processor " + index); - bibEntry.setField(StandardField.BOOKTITLE, "FPGAs for Custom Computing Machines, 1994. Proceedings. IEEE Workshop on " + index); - bibEntry.setField(StandardField.YEAR, "199" + index); - bibEntry.setCiteKey("nanoproc199" + index); - return bibEntry; } - public void clear(DBMSConnection dbmsConnection) throws SQLException { - TestManager.clearTables(dbmsConnection); - } } diff --git a/src/test/java/org/jabref/logic/shared/TestConnector.java b/src/test/java/org/jabref/logic/shared/TestConnector.java index 78f078ce2f9..904dd336ff1 100644 --- a/src/test/java/org/jabref/logic/shared/TestConnector.java +++ b/src/test/java/org/jabref/logic/shared/TestConnector.java @@ -6,6 +6,9 @@ import org.jabref.model.database.shared.DBMSType; import org.jabref.testutils.category.DatabaseTest; +/** + * Stores the credentials for the test systems + */ @DatabaseTest public class TestConnector { @@ -15,18 +18,15 @@ public static DBMSConnection getTestDBMSConnection(DBMSType dbmsType) throws SQL } public static DBMSConnectionProperties getTestConnectionProperties(DBMSType dbmsType) { - if (dbmsType == DBMSType.MYSQL) { - return new DBMSConnectionProperties(dbmsType, "localhost", dbmsType.getDefaultPort(), "jabref", "root", "", false, ""); + switch (dbmsType) { + case MYSQL: + return new DBMSConnectionPropertiesBuilder().setType(dbmsType).setHost("127.0.0.1").setPort(3800).setDatabase("jabref").setUser("root").setPassword("root").setUseSSL(false).setAllowPublicKeyRetrieval(true).createDBMSConnectionProperties(); + case POSTGRESQL: + return new DBMSConnectionPropertiesBuilder().setType(dbmsType).setHost("localhost").setPort(dbmsType.getDefaultPort()).setDatabase("postgres").setUser("postgres").setPassword("postgres").setUseSSL(false).createDBMSConnectionProperties(); + case ORACLE: + return new DBMSConnectionPropertiesBuilder().setType(dbmsType).setHost("localhost").setPort(dbmsType.getDefaultPort()).setDatabase("xe").setUser("travis").setPassword("travis").setUseSSL(false).createDBMSConnectionProperties(); + default: + return new DBMSConnectionPropertiesBuilder().createDBMSConnectionProperties(); } - - if (dbmsType == DBMSType.POSTGRESQL) { - return new DBMSConnectionProperties(dbmsType, "localhost", dbmsType.getDefaultPort(), "jabref", "postgres", "", false, ""); - } - - if (dbmsType == DBMSType.ORACLE) { - return new DBMSConnectionProperties(dbmsType, "localhost", dbmsType.getDefaultPort(), "xe", "travis", "travis", false, ""); - } - - return new DBMSConnectionProperties(); } } diff --git a/src/test/java/org/jabref/logic/shared/TestManager.java b/src/test/java/org/jabref/logic/shared/TestManager.java index 2b8f1f4d2f5..f49d226157c 100644 --- a/src/test/java/org/jabref/logic/shared/TestManager.java +++ b/src/test/java/org/jabref/logic/shared/TestManager.java @@ -1,13 +1,8 @@ package org.jabref.logic.shared; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Stream; +import java.util.Objects; -import org.jabref.logic.shared.exception.InvalidDBMSConnectionPropertiesException; import org.jabref.model.database.shared.DBMSType; /** @@ -16,21 +11,15 @@ */ public class TestManager { - public static Collection getDBMSTypeTestParameter() { - - Set dbmsTypes = new HashSet<>(); - for (DBMSType dbmsType : DBMSType.values()) { - try { - TestConnector.getTestDBMSConnection(dbmsType); - dbmsTypes.add(dbmsType); - } catch (SQLException | InvalidDBMSConnectionPropertiesException e) { - // skip parameter - } - } - return dbmsTypes; + /** + * Determine the DBMSType to test from the environment variable "DMBS". In case that variable is not set, use "PostgreSQL" as default + */ + public static DBMSType getDBMSTypeTestParameter() { + return DBMSType.fromString(System.getenv("DBMS")).orElse(DBMSType.POSTGRESQL); } public static void clearTables(DBMSConnection dbmsConnection) throws SQLException { + Objects.requireNonNull(dbmsConnection); DBMSType dbmsType = dbmsConnection.getProperties().getType(); if (dbmsType == DBMSType.MYSQL) { @@ -51,14 +40,4 @@ public static void clearTables(DBMSConnection dbmsConnection) throws SQLExceptio } } - static Stream getTestingDatabaseSystems() throws InvalidDBMSConnectionPropertiesException, SQLException { - Collection result = new ArrayList<>(); - for (DBMSType dbmsType : getDBMSTypeTestParameter()) { - result.add(new Object[]{ - dbmsType, - TestConnector.getTestDBMSConnection(dbmsType), - DBMSProcessor.getProcessorInstance(TestConnector.getTestDBMSConnection(dbmsType))}); - } - return result.stream(); - } } diff --git a/src/test/java/org/jabref/model/entry/CanonicalBibEntryTest.java b/src/test/java/org/jabref/model/entry/CanonicalBibEntryTest.java index 6d692112871..2bae2288167 100644 --- a/src/test/java/org/jabref/model/entry/CanonicalBibEntryTest.java +++ b/src/test/java/org/jabref/model/entry/CanonicalBibEntryTest.java @@ -14,7 +14,8 @@ void canonicalRepresentationIsCorrectForStringMonth() { BibEntry entry = new BibEntry(); entry.setMonth(Month.MAY); assertEquals("@misc{,\n" + - " month = {#may#}\n" + + " month = {#may#},\n" + + " _jabref_shared = {sharedId: -1, version: 1}\n" + "}", CanonicalBibEntry.getCanonicalRepresentation(entry)); } @@ -26,7 +27,7 @@ public void simpleCanonicalRepresentation() { e.setField(StandardField.TITLE, "def"); e.setField(StandardField.JOURNAL, "hij"); String canonicalRepresentation = CanonicalBibEntry.getCanonicalRepresentation(e); - assertEquals("@article{key,\n author = {abc},\n journal = {hij},\n title = {def}\n}", + assertEquals("@article{key,\n author = {abc},\n journal = {hij},\n title = {def},\n _jabref_shared = {sharedId: -1, version: 1}\n}", canonicalRepresentation); } @@ -36,6 +37,6 @@ public void canonicalRepresentationWithNewlines() { e.setCiteKey("key"); e.setField(StandardField.ABSTRACT, "line 1\nline 2"); String canonicalRepresentation = CanonicalBibEntry.getCanonicalRepresentation(e); - assertEquals("@article{key,\n abstract = {line 1\nline 2}\n}", canonicalRepresentation); + assertEquals("@article{key,\n abstract = {line 1\nline 2},\n _jabref_shared = {sharedId: -1, version: 1}\n}", canonicalRepresentation); } }