Skip to content

L2 & Integrated - tests refactoring#79

Open
ndobromirov wants to merge 55 commits intolcache:mainfrom
ndobromirov:l2-tests-refactoring
Open

L2 & Integrated - tests refactoring#79
ndobromirov wants to merge 55 commits intolcache:mainfrom
ndobromirov:l2-tests-refactoring

Conversation

@ndobromirov
Copy link
Copy Markdown
Contributor

@ndobromirov ndobromirov commented Dec 30, 2016

Overview

This pull request is continuing the work on tests outlined in #43, namely L2 and the Integration parts.
All the tests from LCacheTest class are ported to the new structure.
The whole thing is open for reviews.

Additional things included:

  • All the tests for the integration layer are done.
  • All the integration tests are run against all the combinations of L1 and L2 drivers.
  • Many compatibility fixes between the StaticL2 and DatabaseL2.
  • Improved queued delete on DatabaseL2 - enforced consistency instead of eventual.
  • Improved some SQL queries (many into one) on DatabaseL2.
  • Full test coverage of the state L1 drivers. All are compliant, after some tweaks.
  • Improved L1 cache tests to be run against all StateL1 implementations.
  • Fixed many TODOs left in the code.
  • Improved multiple in-line docks.
  • Tests segregation in groups: state, l1, l2, integration.

It would be nice to have reviews at some point, as I will not have much time to fix stuff starting from next week :).

Summary

There is a new L2CcheTest base class. It is used for generic L2 tests. Specific ones are implemented in either the tests/L2/StaticTest (0) or tests/L2/DatabaseTest (2) classes.

All quirks needed to have DB connection and schema are implemented in a trait and moved out of the new test classes. This trait should be reusable on the Integration layer tests in near future.

There is a new factory class that is to be used with L2 hierarchy. POC for using the factory is done on the newly created tests.

Successfully moved tests to the new L2 structure are removed from the LCacheTest class.

Current implementation follows this idea:

  • StateL1 drivers are fully compliant with each other.
  • All CacheL1 are tested against all StateL1s, so every combination works consistently.
  • All CacheL2 are tested against all CacheL1 (with the recommended default state for it). This way we are reducing combinatory complexity for the tests, without sacrificing the stability much.

Issues faced (for now)

  1. FIXED The method DatabaseTest::testCleanupAfterWrite is fully L2 based, but not compatible with the StaticL2 driver. When I've attempted to make the static compatible with it I faced the issue described in the comment from issue Refactoring tests. #43. Personally I think this should be fixed, so all L2 drivers will be enforced as much as possible common tests. The static driver now clears old events on an address.
  2. FIXED There are too few L2-only tests in the code-base (compared to L1). Most of the tests left in LCacheTest class are covering the Integration layer and thus the L2 drivers partially, as they are a sub-system there. The problem here is that L2 is more complex than L1 and it has less tests purely for it. This is likely the issue that caused the the divergence between Static and Database L2 implementations.
  3. FIXED Pick a direction, both are done. There is an inconsistency between L2 implementations of L2::applyEvents. To comply with the old tests at the moment they are both behaving as the StaticL2 - returning the last event ID always. For DB though this is a performance regression, as it will cause a bit more data to be read form the DB, as it includes the current pool (once). There is a branch that both L2 instances behave as the optimal DatabaseL2 (same as in master) here, but then there is the inconsistency for sync of local pools and remote ones. We need to decide how to proceed. Personally I prefer the solution present, where both are as the StaticL2, as the final API is more logical. When applyEvents is called the L1 will have the latest event as current, without considering locality. The overhead will be smaller and smaller based on the amount of servers handling L1. The read overhead is only on the first sync.
  4. NOT RELATED (open a separate issue / PR for it) Decide a direction for a fix on LCache\L1\APCuTest::testNegativeCache as the APCu is the only incompatible implementation there. This was left as is, as it is not part of the tests refactoring but an edge case of the APCu driver for L1. Should be fixed separately. The refactoring on tests just uncovered it.

"sami-install": "mkdir -p $HOME/bin && curl --output $HOME/bin/sami.phar http://get.sensiolabs.org/sami.phar && chmod +x $HOME/bin/sami.phar",
"test": "phpunit"
"test": "phpunit",
"test-failing": "phpunit --group failing"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was added, so when a new annotation is added to a test like:
@group failing
The composer test-failing command will trigger only that test.
Useful when debugging a single test.

$sth = $this->dbh->prepare($sql);
foreach (array_keys($this->address_delete_queue) as $i => $address) {
$event_id = $this->address_delete_queue[$address];
$sth->bindValue($i * 2 + 1, $event_id, \PDO::PARAM_INT);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A very small micro-optimisation could be to swap th *2 to <<1.

$sql = 'SELECT "event_id", "pool", "address", "value", "created", "expiration"'
. ' FROM ' . $this->eventsTable
. ' WHERE "event_id" > :last_applied_event_id'
. ' AND "pool" <> :exclude_pool'
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was changed to make both L2 drivers to behave consistently.
Though this change is causing small performance regressions, it is behaving as the StaticL2 does at the moment.
There is a non-merged implementation that makes them like the DB here

I will maintain both branches until it is decided how L2::applyEvents should behave and merge the correct version.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will review and decide today.

@ndobromirov ndobromirov mentioned this pull request Jan 5, 2017
@ndobromirov
Copy link
Copy Markdown
Contributor Author

The disablement will cause that the parameter to be ignored and collect ALL garbage instead of only LIMIT items.

@ndobromirov ndobromirov changed the title L2 tests refactoring L2 & Integrated - tests refactoring Jan 8, 2017
@coveralls
Copy link
Copy Markdown

coveralls commented Jan 8, 2017

Coverage Status

Coverage remained the same at 100.0% when pulling b9e4118 on ndobromirov:l2-tests-refactoring into 275282b on lcache:master.


namespace LCache;

final class Integrated
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

many of the Integration level tests were implemented by knowing the inner workings of L1 and L2 and tweaking the inner objects directly, ruining the encapsulation idea of the class and also consistent factory interface implementation for all the tests.

By removing the final here I did a descendant (IntegratedMock) that added many of the needed accessors to port the tests as close as possible.

$state = $hasApcu ? new StateL1APCu("sqlite-$pool") : new StateL1Static();
$cache = new SQLiteL1($pool, $state);
return $cache;
$stateDriver = function_exists('apcu_fetch') ? 'apcu' : 'sqlite';
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added APCu and SQLite as this are the ones that need to be done at some point. I am trusting the fall-back in the factory to instantiate static instead of SQLite state manager for now.

* @return boolean
* True on success.
*/
public function pruneReplacedEvents()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider moving this to the base L2 class.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

$this->assertFalse($this->getEntry(1)->isExpired());

// TODO: How to classify this one?
$this->assertFalse($this->getEntry(0)->isExpired());
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is expired and getTTL are a bit strange for relative times. They are the same for older and newer times, bu the difference comes from the current moment in time. The function getTTL says it has no time to live (but it is not dead yet) and the isExpired follows the same idea at the moment.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to remove the concept of TTLs everywhere possible. They're harder to reason about and test than plain expiration timestamps.

. '"created" INTEGER NOT NULL)');
$this->dbh->exec('CREATE INDEX ' . $prefix . 'latest_entry ON ' . $prefix . 'lcache_events ("address", "event_id")');

// @TODO: Set a proper primary key and foreign key relationship.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still relevant? There are already PK and FK and an index.

Copy link
Copy Markdown
Member

@davidstrauss davidstrauss Jan 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. It's to establish a proper CASCADE DELETE. If you don't have that, tags on expired and deleted items will currently persist. That's fine for testing (as you won't get incorrect data), but it would leave orphaned data in production.

To fix it, we need to add REFERENCES lcache_events(event_id) ON DELETE CASCADE (observing $prefix for lcache_events) to the column lcache_tags.event_id.

Copy link
Copy Markdown
Contributor Author

@ndobromirov ndobromirov Jan 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the tests it's already there some lines below... So is the TODO still relevant?
FOREIGN KEY("event_id") REFERENCES ' . $prefix . 'lcache_events("event_id") ON DELETE CASCADE)');
The cascade delete is used and proven working in the tag delete tests for DatabaseL2 here in this PR.

@davidstrauss
Copy link
Copy Markdown
Member

I'm doing a bunch of work on this now, including some major refactoring. It's mostly hierarchical namespaces and isolating the synchronization status from hit/miss statistics. I'm working from the branch for this pull request (taken earlier today), but it might be tough to incorporate other changes until I've finished the work. Just letting you know.

@ndobromirov
Copy link
Copy Markdown
Contributor Author

Nice to hear :) that the PR is going in, one way or the other.

@ndobromirov
Copy link
Copy Markdown
Contributor Author

Are your changes ready?

Any chances for this to be merged at some point?

If so, we can push this a bit more and make Memcached / Redis L1 drivers and make this one a production ready library.

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.

3 participants