Conversation
|
|
|
Note: although settings have a hierarchical appearance (i.e. their keys are structured with dots like |
|
This is ready to merge, as per discussion in IRC:
|
Travis disagrees, looking into it. |
|
It should be good now... |
Codecov Report
@@ Coverage Diff @@
## master #805 +/- ##
==========================================
+ Coverage 68.59% 68.66% +0.06%
==========================================
Files 179 188 +9
Lines 6289 6385 +96
==========================================
+ Hits 4314 4384 +70
- Misses 1975 2001 +26
Continue to review full report at Codecov.
|
Codecov Report
@@ Coverage Diff @@
## master #805 +/- ##
=========================================
- Coverage 68.94% 68.7% -0.25%
=========================================
Files 182 190 +8
Lines 6401 6508 +107
=========================================
+ Hits 4413 4471 +58
- Misses 1988 2037 +49
Continue to review full report at Codecov.
|
townsend2010
left a comment
There was a problem hiding this comment.
I've only begun testing this and haven't really looked at the code yet. So first issue I hit is that when I start this version of the daemon, it does not see any already existing instances. Once that is fixed, I'll go from there:)
|
oO really ? let me check |
|
@townsend2010 thanks for catching that, now I understand why I kept downloading images when switching branches :) (thought they were updating a lot) I have pushed the fix. The issue was that, by setting organization name and domain in Although the organization name can be passed directly to Note that this code will still have to change with #776 (the client will need access to both its and the daemon's settings file) and possibly for yaml backend. |
|
Cool, thanks. There is also a merge conflict happening now too. |
4ec7d74 to
e607fbd
Compare
|
@townsend2010 rebased and force-pushed |
gerboland
left a comment
There was a problem hiding this comment.
Hey, I really like the end result, the static Settings is very nice to use. It's great that it is mockable too.
But the complexity of the implementation does cause me concern. I've not used static polymorphism before. For instance my approach to this would be more dynamically polymorphic (i.e virtuals!):
class SettingsInterface {
SettingsInterface& instance() { // return Settings or MockSettings, depending on what we want }
virtual QString get(QString key) = 0; ...
}
class Settings: public SettingsInterface { /* concrete impl using QSettings internally */ }
class MockSettings: public SettingsInterface { /* mockable impl */ }
It's a paradigm I'm more accustomed to, but I am always open to new things.
Question: What is the problem that PrivatePass is solving? Is it that it ensures any Singleton will actually be a singleton, i.e. non-copyable, by creating a compiler error if mis-used?
If so, that pretty cool. But the quantity of boilerplate involved, plus the fact it's hard to test (since there's no good way to consider a compile fail as a test pass), makes me worry the same problem can be solved in a simpler but less powerful way.
Options I can think of are:
-
going super-simple: just using QSettings everywhere is easy until we want to test, as we'd have to resort to trickery (muck with files), which I suspect will be awkward. So I'm ok with discarding that approach.
-
The dynamic polymorphism approach I gave above should be do-able IMO, and a bit simpler too. Did you consider it, and why did you dismiss it?
Were there other approaches you considered?
I want to understand why you consider this the best approach, just to be sure that the boiler-plate is worth it.
|
@gerboland fair questions, I started typing answers a couple of times but I am not inspired ATM, I'll have to reply later. Meanwhile I pushed fixes for compilation in buggy compilers ;) |
|
@gerboland sorry for the long reply. I hope it is clear, but let me know if you would like to chat about this or if there are particular places in the code that I could explain better. What brought me to this approach was really the combination of:
As you suggest, let's do away with option 1 (direct So we needed a wrapper. Your option 2 (regular interface + inheriting implementation and mock classes), was my departure point. It would normally mean passing OK, so what if we just dropped the param but created a new object whenever needed? Well, we could not use the ctor directly if we wanted to mock it, because which type would we use? We would need some sort of factory. This factory would be an interface with an implementation for tests and one for real code. But which one do we call? Well, we'd need to pass one factory implementation in normal execution and another in tests. But for that we'd need to inject a dependency everywhere again. Basically we'd be back to the same problem as before. What we needed was a way to say "here is the settings object, everyone can use it", in a single place. We'd determine the exact object (and thus its type) only once. The Settings singleton does that. By default it provides a "real" Settings object: one of its own implementation, who does "the real job" we need. But it allows registering a derivation that overrides things for mocking. Note that this is totally dependent on the dynamic polymorphism (the usual sort). The The rest is the implementation of the To ensure that only one object is created, the So we use a different mechanism to restrict ctor calling that does not rely on visibilities at all. We actually declare it public, but give it a token parameter that callers need to be able to produce (here). Descendants then require this same token in their ctor and pass it to the parent ctor. We keep a single such token private in the Since the Singleton is the one creating the object, it needs to know its derived type. It gets that in the template parameter. But because we also allow mocking, we allow an additional template for the mock. That is the bit that introduces more template complexity. The final bit is the I suspect part of the "boiler-plate" you mention is due to separation of declaration from definition (even if the implementation is in the header). This is arguably less important today, with As for testability, it can be tested by attempting to compile something and see if it breaks. I provided examples in An alternative I thought of was a free factory function that would be "premocked" or have a header injected. Both sounded hackish and I was unsure if they were doable (I am not familiar with premock). Another alternative that occurs to me now would be to provide copies or a static "template" object, rather than a reference to it as in a singleton. We would have to return |
|
Note to self: merge master and check if/where the new QString/fmt util can be used. TODO @ricab |
Tests currently failing.
Test still failing.
Tests still failing.
Cosmetics: the order was almost alphabetical, but not quite, so fix that
Uses QSettings directly. No defaults yet.
Sets these QCoreApplication elements in CLI client's main. Drops them from QSettings ctor.
Sets app, org, and domain on remaining binaries (systray still missing).
Drop petenv name constant and get it from the settings everywhere it is used.
This implements the check directly in the Settings class. Such checks should eventually be moved out to registered settings observers or similar.
Makes the last remaining test pass.
To get meaningful messages if the issue arises accidentally outside of the context of `get` or `set` commands.
Refrain from setting these centrally, to avoid changing results from QStandardPaths (and so maintain previous record paths).
Avoids paths with 'Unknown Organization'.
VC++ would not accept WithArg (see https://is.gd/rT7foD), so replaced plain gmock actions with lambda.
7ce0df8 to
a16be82
Compare
gerboland
left a comment
There was a problem hiding this comment.
I'm happy to take this as-is. It's a nice piece of code. Thank you!
townsend2010
left a comment
There was a problem hiding this comment.
This is some solid, good looking code and I learned some things from this:)
bors r=gerboland,townsend2010
Build succeeded |
This fixes #766