Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The Great Backup Refactor of 2015 #936

Merged
merged 52 commits into from
Jan 19, 2016
Merged

The Great Backup Refactor of 2015 #936

merged 52 commits into from
Jan 19, 2016

Conversation

willmot
Copy link
Contributor

@willmot willmot commented Nov 6, 2015

This PR introduces a major refactor of the Backups class with the express aim of separating concerns, reducing complexity and improving testability.

This is achieved by splitting the Backups class into several smaller classes, each responsible for different parts of the backup process.

The core backup functionality (mysqldump, zip, zipArchive) is now handled by a set of Backup_Engine's, one for each backup method.

These Backup_Engines are then brought together in Site_Backup, which takes responsibility for combining the database and file backups as well as looping through the available backup engines.

The Refactor

Backup Engines

1. Backup_Engine

The backup engine concept has two levels of inheritance. Starting at it's base with the Backup_Engine class. This is both a utility class and a template and is responsible for:

  • Setting the memory_limit and set_timeout settings so the backup can run uninterrupted.
  • Storing the backup filename and filepath.
  • Setting the directory which the backup will be stored in.
  • It defines an abstract function verify_backup

2. File_Backup_Engine & Database_Backup_Engine

The next 2 classes both extend Backup_Engine. These are each responsible for generic functionality related to Database and File backups.

  • Database_Backup_Engine is responsible for parsing database connection details and making them available to be used by the eventual backup methods. It's also defines the verify_backup method which is used to verify that any database backup methods completed successfully.
  • File_Backup_Engine is responsible managing the excludes functionality including default excludes. It provides methods for dealing with the files intended for backup and provides a file specific implementation of verify_backup.

3. Zip_File_Backup_Engine, Zip_Archive_File_Backup_Engine, Mysqldump_Database_Backup_Engine, IMysqldump_Database_Backup_Engine.

With all the shared functionality handled by the parent classes we're left with very lean final backup method implementations. At their core a backup method need only implement the backup method which should return $this->verify_backup.

One huge benefit to this structure is that it's now much, much easier to add new backup methods. We could easily add Tar support, or bring back PclZip with much less overhead and no increase in code complexity outside of the specific Backup_Method implementations. For example

class Tar_File_Backup_Engine extends File_Backup_Engine {

    public function __construct() {
        parent::__construct();
    }

    public function backup() {
        // Perform Tar Backup...
        return $this->verify_backup();
    }
}

This PR also splits out a bunch of other functionality from both the Scheduled_Backup and Backup classes:

  • The various static utility methods have been split out into a Backup_Utilities class.
  • Excludes are now handled by Excludes.
  • The site size functionality is now handled by Site_Size.
  • The backup status tracking is now handled by Backup_Status.

Unit Testing

A huge driver behind this refactor was a desire to make the core backups functionality much more testable. The current implementation has been written entirely to tests.

The abstract Backup_Engine, File_Backup_Engine and Database_Backup_Engine classes are all tested indirectly using mock backup engine implementations.

Another key aim was that we should run the same set of common backup engine unit tests across all backup method implementations without having to duplicate the tests, this is achieved with the Common_Database_Backup_Engine_Tests & Common_File_Backup_Engine_Tests parent test cases. These test cases are not run directly, instead the are extended by a specific backup engine implementations tests. For example:

<?php

namespace HM\BackUpWordPress;

class Zip_File_Backup_Engine_Tests extends File_Backup_Engine_Common_Tests {

    protected $backup;

    public function setUp() {
        $this->backup = new Zip_File_Backup_Engine;
        parent::setUp();
    }


}

This test case is then run directly. Any tests unique to the Zip_File_Backup_Method would be implemented here. The specific backup engine is then passed up to the parent by calling parent::setUp. Again the real benefit here is that a new backup engine automatically get's a whole suit of unit tests just be implementing the above barebones test case. This hugely reduces the amount of copy paste tests that we currently have.

As part of implementing this unit tests I've already discovered and fixed several bugs that are present in the current Backup class.

The test data is now created and destroyed programatically instead of being stored on the filesystem, this ensures we can rely on the data being exactly as we expect.

TODO

There is a still a bunch of things todo to finish off the work here:

  • Dockblocks need moving over from the existing Backup class, and in a lot of cases, updating.
  • The top level Backup_Engine class currently has all the error and warning handling. This should probably be moved to a separate Backup_Errors class and be refactored to use WP_Error.
  • There is no backwards compatibility with the old hmbkp_ actions.
  • We could consider splitting the static utility functions into their own Backup_Utility class.
  • Back compat with the old get_archive_method and get_mysqldump_method methods (if they are needed).
  • Site size calculations
  • Site file list folder sizes
  • Backup status
  • Backup errors and warnings
  • We should aim for at least 80% coverage of this new code before we merge.
  • Move over the remaining unit tests from the old Backup class.
  • Remove any old unit tests that are no longer relevant.
  • Fixup issues reported by Scrutiniser
  • Add in a filter so that 3rd parties can add their own Backup_Engine implementations into the list.
  • The database backup is not currently passed into the file backups, need to find a way to do this.
  • The Backup_Director needs to be able to take arguments that need to be set on the Backup_Engine implementations (for example setting new excludes).
  • Integrate with Scheduled_Backup so that the main plugin uses the new code.
  • Remove the old Backup class.
  • PATH shouldn't be a singleton and should instead be passed around.
  • File_Backup_Engines should take the directory to backup and the output filepath as constructor args. Database_Backup_Engines should just take the output filepath.
  • The root Backup_Engine class should probably go away (error handling should be a separate class that's injected as a dependancy and upping the memory limit should be handled by the caller.
  • Database_Backup_Engine and File_Backup_Engine should maybe be implementations. The shared functionality could be passed in by the caller.
  • There should be a Files class which handles interactions with Finder.
  • Fixup the Email & Webhook Services.
  • No longer firing Services::action, is that an issue?
  • No longer storing errors and warnings in .backup_errors and .backup_warnings files, were they used?

This initial commit makes a good start towards a major refactor of the backups class. At a high level:

- Splits the actual backup logic into `Backup_Engine` with utility functions shared via base classes.
- A focus on unit testing from the start, this refactor was writtin to unit tests only at this point
- Multiple `Backup_Engines` are then passed into a `Backup_Director` which is responsible for waterfalling through the engines until it finds one which works
- This isn't yet integrated into the main plugin, unit tests only for now.
- There's still lots to figure out, error handling, the old `hmbkp_` actions, integrating with `Scheduled_Backup`, more unit testing etc.
@willmot willmot changed the title The great Backup refactor of 2015 The Great Backup Refactor of 2015 Nov 6, 2015
You can't pass a method directly to `empty`
- fixes a fatal error in Database_Backup_Engine
- fixes up the unreadable_files unit tests and updates get_files() so it passes
- remove some dead code
…_Engine` too it.

Make `Path::get_path()` static so we don't have to do `Path->get_instance()::get_path()`

Adds unit tests for `get_home_path` and refactors the methos to pass them, fixes #695
Move the root path handling to Path and update unit tests

More work to integrate the backup engines into the rest of the plugin
Continues work refactoring Scheduled_Backup away from Backup.
Moves the unit tests over and updates our Finder implementation to pass them
- Moves all site size handling to a new Site_Size class and out of `Schedule`.
- Move all excludes handling out of `File_Backup_Engine` into a new `Excludes` class.
- Introduces a `Site_Backup` class which is responsible for the orchestration of the entire site backup.
- Rounds out the `Backup_Director` class and adds unit tests.

Updates all existing code to reference new classes and introduces unit tests for all of the above.
Improved unit tests around Site backups, file backups and excludes.
@willmot willmot self-assigned this Nov 21, 2015
@willmot willmot added this to the Future Release milestone Nov 21, 2015
Includes a bunch of minor changes to both unit tests and plugin based on
issues discovered along the way. Mostly to do with windows paths having
forward slashes instead of backslashes.
@willmot
Copy link
Contributor Author

willmot commented Dec 1, 2015

Put in a bunch of effort to get the unit tests running cleanly and passing on Windows. I'm also going to writeup a guide on how to get unit testing running on Windows as there isn't much out there.

Includes a few minor changes to the class in response to the tests, also cleans up whitespace in the backup status class.

Moves the backup path tests into root as there's no point them being in a folder on their own.
- Unit tests for the site_size class
- Move list_directory_by_filesize out of the Site_Size class, it's just a function.
- Allow for small byte variences when comparing filesizes in unit tests
- Introduce a helper function that returns a cross OS compatible way to pipe stderr to null
- Split filesize into a bunch of shorter more focused functions.
@willmot
Copy link
Contributor Author

willmot commented Jan 19, 2016

Chatted to @pdewouters, we're going to introduce back combat methods on Site_Backup so that extensions continue to work without changes.

Ensure the API we expose to Extensions stays backwards compatible.

- Renames Site_Backup back to Backup as the extensions use type hinting to ensure that the Backup object is of type `Backup`.
- Introduce a back compat `Backup::get_archive_filepath()` which fires a deprecated warning and calls `Backup::get_backup_filepath()`.
- Introduce a back compat `Scheduled_Backup::set_status()` which fires a deprecated warning and calls `Scheduled_Backup->status::set_status()`.
- Introduce a back compat `hmbkp_add_settings_error` which fires a deprecated warning and calls `add_settings_error`.

Tested the existing versions of all our extensions and they now work as intended.
@willmot
Copy link
Contributor Author

willmot commented Jan 19, 2016

In 7031a2b we're now 100% back compatible with existing extensions.

willmot added a commit that referenced this pull request Jan 19, 2016
The Great Backup Refactor of 2015
@willmot willmot merged commit 11eccae into master Jan 19, 2016
@willmot willmot deleted the backup-engines branch January 19, 2016 13:44
@willmot
Copy link
Contributor Author

willmot commented Jan 19, 2016

\0/

@willmot willmot modified the milestones: 3.4, Future Release Jan 19, 2016
willmot added a commit that referenced this pull request Feb 19, 2016
…ing a backup.

There were several issues contributing to this bug.

1. We weren't even running `Path::cleanup` before and after running a backup, we now are.
2. We were missing a unit test to verify that when backing up the database, no other files are included. We now have a unit test.
3. We were missing unit tests for `Path::cleanup` to ensure that all the files we'd expect to are actually cleaned up. We now have unit tests.
4. Those tests exposed the fact that `Path::cleanup` wasn't cleaning up `ZipArchive` temporary files. It now does.
5. `PATH::reset_path` wasn't correctly resetting the `Path::custom_path` meaning that `PATH::cleanup` wouldn't run in some situations. That's now fixed.

So in summary, before this commit temporary files from failed backups were not correctly deleted. After this commit they will be deleted.

This bug was almost certainly introduced in #936
willmot added a commit that referenced this pull request Feb 19, 2016
…ing a backup.

There were several issues contributing to this bug.

1. We weren't even running `Path::cleanup` before and after running a backup, we now are.
2. We were missing a unit test to verify that when backing up the database, no other files are included. We now have a unit test.
3. We were missing unit tests for `Path::cleanup` to ensure that all the files we'd expect to are actually cleaned up. We now have unit tests.
4. Those tests exposed the fact that `Path::cleanup` wasn't cleaning up `ZipArchive` temporary files. It now does.
5. `PATH::reset_path` wasn't correctly resetting the `Path::custom_path` meaning that `PATH::cleanup` wouldn't run in some situations. That's now fixed.

So in summary, before this commit temporary files from failed backups were not correctly deleted. After this commit they will be deleted.

This bug was almost certainly introduced in #936
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant