*** alecm has quit IRC | 00:11 | |
*** philiKON has joined #zope3-dev | 00:40 | |
*** tisto has quit IRC | 00:48 | |
*** JaRoel|4D has quit IRC | 01:23 | |
*** allisterb_ has joined #zope3-dev | 01:26 | |
*** drudi has joined #zope3-dev | 01:31 | |
*** allisterb has quit IRC | 01:55 | |
*** aaronv has joined #zope3-dev | 01:59 | |
*** JaRoel|4D has joined #zope3-dev | 02:08 | |
*** aaronv has quit IRC | 02:12 | |
*** sunoano has quit IRC | 02:23 | |
*** sunoano has joined #zope3-dev | 02:24 | |
*** pcardune has joined #zope3-dev | 02:36 | |
*** aaronv has joined #zope3-dev | 02:37 | |
*** aaronv has quit IRC | 02:52 | |
*** pcardune has quit IRC | 02:53 | |
*** redir has quit IRC | 02:59 | |
*** ignas has quit IRC | 03:18 | |
*** alga has quit IRC | 03:50 | |
*** romanofski has joined #zope3-dev | 05:04 | |
*** jamur2_ has joined #zope3-dev | 05:06 | |
*** J1m has quit IRC | 05:07 | |
*** jamur2_ has quit IRC | 05:09 | |
*** pcardune has joined #zope3-dev | 06:08 | |
*** afd__ has quit IRC | 06:16 | |
*** afd__ has joined #zope3-dev | 06:17 | |
*** philiKON_ has joined #zope3-dev | 06:28 | |
*** kaeru_ has joined #zope3-dev | 06:39 | |
*** kaeru has quit IRC | 06:44 | |
*** philiKON has quit IRC | 06:45 | |
*** kaeru_ has quit IRC | 07:20 | |
*** redir has joined #zope3-dev | 07:22 | |
*** srichter has joined #zope3-dev | 07:28 | |
*** ChanServ sets mode: +o srichter | 07:28 | |
*** romanofski has quit IRC | 07:41 | |
*** pcardune has quit IRC | 07:50 | |
*** pcardune has joined #zope3-dev | 08:08 | |
*** drudi has quit IRC | 08:11 | |
*** pcardune has quit IRC | 08:12 | |
*** pcardune has joined #zope3-dev | 08:18 | |
*** pcardune has quit IRC | 08:24 | |
*** kursor has joined #zope3-dev | 08:30 | |
*** srichter_ has joined #zope3-dev | 08:33 | |
*** srichter has quit IRC | 08:34 | |
*** dunny has joined #zope3-dev | 08:39 | |
*** kursor has left #zope3-dev | 08:53 | |
*** srichter_ has quit IRC | 09:46 | |
*** sunoano has quit IRC | 10:07 | |
*** afd_ has joined #zope3-dev | 10:11 | |
*** afd__ has quit IRC | 10:11 | |
*** malthe|Zzz is now known as malthe | 10:15 | |
*** romanofski has joined #zope3-dev | 10:18 | |
*** redir has quit IRC | 10:23 | |
*** sunoano has joined #zope3-dev | 10:27 | |
*** Theuni1 has joined #zope3-dev | 10:31 | |
*** tisto has joined #zope3-dev | 10:48 | |
*** romanofski has quit IRC | 10:54 | |
*** kursor has joined #zope3-dev | 11:03 | |
*** junkafarian_ has joined #zope3-dev | 11:24 | |
*** kursor has left #zope3-dev | 11:28 | |
*** malthe is now known as malthe|away | 11:33 | |
*** afd_ has quit IRC | 11:34 | |
*** junkafarian_ has quit IRC | 11:35 | |
*** afd__ has joined #zope3-dev | 11:47 | |
*** jhauser has joined #zope3-dev | 12:18 | |
*** pelle_ has joined #zope3-dev | 12:21 | |
*** yota has joined #zope3-dev | 13:19 | |
*** afd__ has quit IRC | 13:39 | |
*** kaeru has joined #zope3-dev | 13:58 | |
*** afd__ has joined #zope3-dev | 14:14 | |
*** romanofski has joined #zope3-dev | 14:50 | |
*** J1m has joined #zope3-dev | 15:13 | |
*** pelle_ has quit IRC | 15:25 | |
*** philiKON_ has quit IRC | 15:36 | |
kobold | J1m: do you have a couple of minutes? | 15:38 |
---|---|---|
J1m | sure | 15:38 |
kobold | J1m: I was offline for two weeks, and I'm reading the backlog of the IRC channel. I read the wonderful work srichter did on zope.release | 15:39 |
J1m | Yup, although there seems to be more to do there. | 15:39 |
kobold | I just wonder if it wouldn't be easier to automate something like: for each package which is part of the ZTK, do a svn check out and run the tests | 15:39 |
kobold | it looks it is quite complex to run all the tests together... | 15:40 |
J1m | Might be. | 15:40 |
kobold | I suppose we need two type of checks: if I modify a package, I want to test all the packages that use it to check if I broke something | 15:40 |
kobold | and we have to ensure that a given set of packages work together | 15:41 |
J1m | It turns out that srichter ran them individually. | 15:41 |
kobold | uh? really? I didn't know it | 15:41 |
J1m | I tried to reproduce his report and got failures. | 15:41 |
kobold | me too | 15:42 |
J1m | when I asked him about it here, it turns out that he was running the tests separately. | 15:42 |
J1m | Then we got into a discussion of how to automate *that*. | 15:42 |
kobold | I have that automatized with buildbot, but it is not working with the versions from the KGS (yet) | 15:43 |
J1m | We either need a facility in zope.kgs (used by zope.release) to run packages separately, or | 15:43 |
*** dunny has quit IRC | 15:43 | |
J1m | we need to update the thing that Theuni1 and others did to make it reproduceable. | 15:44 |
* kobold doesn't know anything about "the thing" :) | 15:44 | |
J1m | hm, cool | 15:44 |
Theuni1 | Btw: wolfgang told me that compattests already does the right thing: it runs the tests of each package against buildout's working set | 15:44 |
J1m | Theuni1, ayt? | 15:44 |
Theuni1 | yes | 15:44 |
Theuni1 | i'm listening | 15:44 |
kobold | Theuni1: hi! | 15:44 |
* Theuni1 waves | 15:44 | |
J1m | What's the name of the testing system you did that is kinda like zope.kgs? | 15:45 |
Theuni1 | z3c.compattest | 15:45 |
J1m | that you did for the grok sprint? | 15:45 |
J1m | ah yes | 15:45 |
J1m | kobold, that. :) | 15:45 |
J1m | So that is an alternative to zope.kgs. | 15:45 |
Theuni1 | I have the feeling that whatever it is that's blocking you from using it is very minor and I just haven't understood what it is yet. | 15:46 |
kobold | so, it looks like switching to z3c.compattest and automatizing it is the solution | 15:46 |
J1m | what I think we need is something that: | 15:46 |
J1m | 1. A definition of standard packages and versions that need to be tested when a change is made, and | 15:46 |
J1m | 2, someting that automates running their tests. | 15:46 |
kobold | IMHO, 2 is more important than 1 | 15:47 |
J1m | 3. and that runs each package's tests in a separate process. | 15:47 |
kobold | because after you have 2, you can start from the bottom of the dependency tree... | 15:47 |
J1m | kobold, well z3c.compattest gives you that. | 15:47 |
kobold | ...and add the packages going up, through the dependencies, to find out the best set of core packages | 15:47 |
J1m | but without 1, it will rot again. | 15:48 |
Theuni1 | 2 and 3 are there. 1 is there in some form that might need tweaking | 15:48 |
kobold | it also gives us 3, it seems | 15:48 |
Theuni1 | But I think 1 is there in a way that helps already. | 15:48 |
Theuni1 | Can we get more specific on how 1 should work? | 15:48 |
J1m | Theuni1, we need 1 and we need it without making people think. | 15:48 |
Theuni1 | jup | 15:48 |
kobold | Theuni1: where can I find z3c.compattest? | 15:48 |
Theuni1 | kobold: actually its z3c.recipe.compattest | 15:48 |
kobold | Theuni1: z3c.recipe.compattest, right? | 15:48 |
Theuni1 | yes | 15:48 |
kobold | ok, found :) | 15:49 |
Theuni1 | You include that in the buildout of the package you're working on | 15:49 |
kobold | Theuni1: ahh | 15:49 |
J1m | Theuni1, there should be some instructions at some url that people can go to and quickly figure out how to run the tests. | 15:49 |
kobold | just reading the README, I like the idea, it is just wonderful | 15:49 |
Theuni1 | J1m: agreed | 15:49 |
J1m | The instructions should be a long the lines of: | 15:49 |
J1m | - check someting out | 15:49 |
J1m | - give some canned commands (e.g. bootstrap, buildout, run a test script | 15:50 |
J1m | - if tests pass, update a specifification file to record a new blessed version | 15:50 |
J1m | although blessing a new ztk thing might need more ceremony. Not sure. | 15:51 |
J1m | A big problem is that people check in tests that pass on one python version and os and fail on another. | 15:51 |
kobold | fine, I'm working on this, let's see if I can get something decent in a reasonable time :) | 15:51 |
J1m | I *hate* fixing other people's windows test failures. :) | 15:52 |
kobold | J1m: you use buildbot for that, you can't really rely on tests run by the developers | 15:52 |
Theuni1 | J1m: and I hate producing them but then again windows is such an annoyance wrt getting a dev environment ... | 15:52 |
J1m | Theuni1, I agree :) | 15:52 |
J1m | kobold, I don't understand what you just said. | 15:53 |
J1m | why can't I rely on tests run by developers? | 15:53 |
kobold | J1m: because nobody run tests on all the python versions and on different platforms before doing a checkin, I suppose | 15:53 |
kobold | test automatization is the only way to ensure that a set of tests pass on different platforms and different python versions | 15:54 |
J1m | right, but we could run buildbots or the equivalent on all python versions and platforms. | 15:54 |
kobold | that's what I said :) | 15:54 |
J1m | But then we need some sort of process that works with that. | 15:54 |
J1m | brainstorming... | 15:55 |
kobold | yep | 15:55 |
J1m | maybe there's stage branch. | 15:55 |
J1m | when you get tests passing you merge to a stage branch. | 15:55 |
Theuni1 | J1m: something that compattest needs is a specification of the controlled packages which need to be tested. unfortunately we don't have a reliable format for that yet. | 15:55 |
kobold | maybe each checkin should trigger a rebuild/test of all the related packages | 15:56 |
J1m | the buildbots run tests on the stage branch and the trunk. | 15:56 |
Theuni1 | I looked at KGS controlled-packages list and versions.cfg but they include things we don't want to test | 15:56 |
J1m | when the bots of run the tests on all of the platforms and python versions opn the stage branch, we merge from that to the trunk. | 15:56 |
J1m | Theuni1, that's not a format issue. | 15:57 |
J1m | That's a scope issue. | 15:57 |
J1m | I agree that the scope of zope.release is wider than it should be. | 15:57 |
Theuni1 | Well... if the scope would be ok we currently would also have a format issue ;) | 15:58 |
Theuni1 | But I agree, it's a scope issue first | 15:58 |
J1m | to solve my problem, I just want to tests the foundation packages that are used in a zope process, | 15:58 |
* kobold was surprised to find lovely.something packages in zope.release, indeed | 15:58 | |
J1m | Theuni1, I don't understand the format issue. | 15:58 |
Theuni1 | J1m: we tried to get a reasonable scope using heuristics for compattest | 15:58 |
J1m | all you need is: | 15:58 |
J1m | 1. A list of packages to test | 15:58 |
Theuni1 | J1m: yeah, the kgs currently produces a versions.cfg which people use to extend their buildout | 15:59 |
J1m | 2. a versions.cfg (or equivalent) that specifies the versions of packages and dependencies. | 15:59 |
Theuni1 | but the versions section will be merged with other stuff | 15:59 |
Theuni1 | so using the versions section as a list isn't right | 15:59 |
J1m | I don't understand. | 15:59 |
Theuni1 | I think we both try to say the same thing. | 16:00 |
J1m | That would be cool. :) | 16:00 |
Theuni1 | Lemme try again: point 1 is a problem because kgs' scope is too broad | 16:00 |
Theuni1 | even if the scope would be fine I couldn't use it currently because the only output from KGS that I can "see" is the versions.cfg | 16:01 |
J1m | Lemme try again. :) | 16:01 |
Theuni1 | i could enumerate the versions section but even if the kgs' scope would be fine it would be cluttered from other places. | 16:01 |
J1m | cluttered from where? | 16:02 |
Theuni1 | e.g. the version pins from dependencies | 16:02 |
J1m | what's wrong with that? | 16:02 |
Theuni1 | Should those be included in the list of packages to tset? | 16:02 |
Theuni1 | test? | 16:02 |
J1m | no | 16:02 |
J1m | not imo | 16:02 |
Theuni1 | that's why i can't enumerate the versions section for determining which packages' tests to run | 16:02 |
Theuni1 | but the versions.cfg is the only output format i currently see | 16:03 |
Theuni1 | (from kgs that is)0 | 16:03 |
J1m | I think you missunderstand the kgs | 16:03 |
Theuni1 | Might be | 16:03 |
J1m | no | 16:03 |
*** drudi has joined #zope3-dev | 16:03 | |
J1m | Just a sec, | 16:03 |
Theuni1 | sure | 16:03 |
J1m | see http://svn.zope.org/zope.release/trunk/releases/controlled-packages.cfg?rev=102349&view=auto | 16:04 |
J1m | This is what's used by kgs. | 16:04 |
J1m | Note that I'm not advocating kgs. | 16:04 |
Theuni1 | ack | 16:05 |
J1m | Here, you have a list of packages and you specify which ones to test. | 16:05 |
J1m | An alternative would be a separate list of packages to test and a versions.cfg. | 16:05 |
J1m | I'm sure there are many other alternatives. | 16:06 |
Theuni1 | We "just" have to pick one specific one that works ;) | 16:06 |
J1m | wrt kgs, zope.kgs is just mechanism. clients of it, like zope.release define a scope. I just say this for clarity, not because I'm advocating it. | 16:07 |
J1m | If I were doing this, I'd probably go with a list of packages to test and a versions.cfg, as that leverages machnery we already have. | 16:07 |
Theuni1 | right | 16:07 |
Theuni1 | that's why i built compattest the way it is | 16:07 |
Theuni1 | i rely on the working set that buildout already has activated + the option to get develop eggs checked out from svn.zope.org svn alternatively | 16:08 |
*** ccomb has joined #zope3-dev | 16:09 | |
Theuni1 | Now I'm trying to find a place where that list of packages already lives so I can reuse that. | 16:09 |
J1m | which list? the ztk list you created? | 16:09 |
J1m | or the one that zope.release uses? | 16:09 |
Theuni1 | Rephrase: I'm trying to find a place where the list (with right scope) of the packages that need to be tested already lives (assuming it already exists). The ZTK list I currently consider work in progress ... maybe that's the list I should be consuming for compattest? | 16:11 |
J1m | Maybe, or you could start from zope.release and remove things. | 16:11 |
Theuni1 | Question about mechanics: will I be able to modify the version section on the fly from a recipe? | 16:12 |
J1m | BTW, I think compattest should probably provide general mechanism and then provide a way to "instantiate" it for specific test sets. | 16:12 |
Theuni1 | ack | 16:13 |
J1m | You shouldn't modify the versions section. Why would you want to? | 16:13 |
Theuni1 | Uhm ... never mind ... I guess the compattest would need to allow specifying which packages to test and the equivalent of the versions section. | 16:14 |
Theuni1 | So it needs to replicate the versions behaviour for its own working set | 16:15 |
J1m | right | 16:15 |
Theuni1 | ok, i think i understand now | 16:15 |
Theuni1 | I'm not sure when I'll have time to do that, but at least I understand what's missing. | 16:16 |
J1m | Note that one approach I was thinking of was to generate a single test script and find a way to tell the test runner to run each package in a separate subprocess. | 16:16 |
Theuni1 | hmm. that's probably hard to get right | 16:17 |
J1m | Now you generate a unit test layer and assign it to layerless tests. | 16:17 |
Theuni1 | the current benefit is that each test runner script really only gets the sys.path include the exactly specified dependencides | 16:17 |
Theuni1 | Me? | 16:17 |
Theuni1 | I don't do that AFAIK | 16:17 |
J1m | Yes, when you refactored the test runner. | 16:17 |
*** kiorky has quit IRC | 16:18 | |
Theuni1 | ah on that end | 16:18 |
J1m | I thought you did, | 16:18 |
Theuni1 | sorry, i thought you were talking about the compattests | 16:18 |
Theuni1 | yes i did | 16:18 |
J1m | so you could instead generate a unit test layer per package. | 16:18 |
Theuni1 | I could, except that the isolation benefit of independent testing wouldn't be there. | 16:18 |
J1m | and use the test runner's facility for running each layer in a subprocess. | 16:18 |
J1m | sure it would | 16:18 |
Theuni1 | how? | 16:19 |
J1m | if each package ran in a separate process. | 16:19 |
Theuni1 | You're missing that the test runner will run with a working set larger than the actual dependencies of the package under testing | 16:19 |
J1m | which it would by giving each it's own layer and using the -j option. | 16:19 |
Theuni1 | so we couldn't find incorrect dependency declarations | 16:19 |
J1m | That's probably a good thing. | 16:19 |
Theuni1 | which is one of the benefits | 16:19 |
Theuni1 | I agree on the approach of splitting up the unit test layer much more, though | 16:20 |
J1m | I guess it depends on what your goals are. | 16:20 |
Theuni1 | wrt to running in parallel | 16:20 |
J1m | But I see your point. | 16:20 |
Theuni1 | We wrote compattest because we were refactoring packages heavily, moving code around and wanted to make sure the consumer packages of that code would still work independently. | 16:20 |
J1m | I manually split up the zodb tests into layers to be able to run them in parallel. | 16:21 |
J1m | right, I get that. | 16:21 |
Theuni1 | I think another approach would be for the test runner to just split layers when they grow larger than X tests | 16:21 |
J1m | perhaps. | 16:21 |
Theuni1 | what we're doing there isn't as much an isolation thing but managing performance tradeoffs | 16:22 |
J1m | The ZEO tests tend to get lots of benefit because they are generally not cpu bound. OTOH, they tend to be very time sensitive, so I can only run them in parallel on fairly fast machines. | 16:23 |
J1m | I need to fix a lot of those tests to be less timing dependent. Those tests are so painful to deal with. But I digress. :) | 16:23 |
*** kiorky has joined #zope3-dev | 16:25 | |
Theuni1 | I'll try to get that compattest stuff done at some point. Need to go back to what I'm actually doing now :) | 16:26 |
J1m | Theuni1, kobold wants to help. | 16:27 |
Theuni1 | Cool. | 16:27 |
J1m | so maybe you can help him help you :) | 16:27 |
Theuni1 | kobold: a pointer would be that the include/exclude-mechanism would need to be replaced by retrieving a list from a file like jim posted (from a zope.release) | 16:28 |
J1m | Although he's been pretty quiet the last few minutes. | 16:28 |
Theuni1 | We'll see :) | 16:28 |
*** tarek has joined #zope3-dev | 16:28 | |
Theuni1 | the second step would be to not rely on the buildouts working set but build a new one based on the versions in that file | 16:28 |
Theuni1 | after that we should be where we want to be, maybe allowing to specify more specific versions for packages that aren't in the list would be nice | 16:29 |
J1m | I don't know what you mean by the buildout's working set. | 16:29 |
J1m | doesn't compattest generate a separate script for each package? | 16:29 |
Theuni1 | yes | 16:30 |
J1m | Each script would have it's own working set, even if you use the buildout machinery to generate it. | 16:30 |
Theuni1 | but it uses the currently running buildout to find out which versions of packages to use | 16:30 |
Theuni1 | Here's what happens: | 16:31 |
Theuni1 | def _working_set(self, package): | 16:31 |
Theuni1 | eggs = zc.recipe.egg.Egg(self.buildout, self.name, dict(eggs=package)) | 16:31 |
J1m | All of the tests should use the same versions. They should each have their own working sets, but when a package is used by 2 different test scripts, the same version should be used. | 16:31 |
Theuni1 | then we extract that working set | 16:31 |
Theuni1 | for each test runner | 16:31 |
Theuni1 | i know | 16:31 |
J1m | cool, so what's the problem? :) | 16:32 |
Theuni1 | Imagine you set up the compattest part two times, then you want each to use different versions, e.g. the 3.4 versions for one and the 3.5 versions for the other | 16:32 |
J1m | ah | 16:33 |
Theuni1 | the set of versions needs to be specified for the part not using buildout's global mechanism | 16:33 |
*** __mac__ has joined #zope3-dev | 16:33 | |
J1m | well, if you want to do that, you should extend zc.recipe.egg to be able to take a versions option. | 16:33 |
Theuni1 | sounds like a good idea :) | 16:34 |
Theuni1 | kobold: did jim and I produce enough text to get you confused? :) | 16:34 |
J1m | when I started working on buildout, I tended to implement options both locally and globally. | 16:34 |
J1m | But that was a lot of work and we rarely needed to override an option locally so I stopped bothering with local options. | 16:35 |
J1m | But is is always an option to add a local override when needed. | 16:35 |
Theuni1 | good to know that history | 16:35 |
J1m | I think we scared kobold away. | 16:35 |
Theuni1 | :/ | 16:35 |
*** sweh has joined #zope3-dev | 16:35 | |
J1m | actually, my irc client shows him as away. | 16:36 |
Theuni1 | Maybe he realized it's sunday after all | 16:36 |
J1m | Maybe he'll come back later and catch up. | 16:36 |
Theuni1 | we'll see | 16:36 |
J1m | Let's both get back to what we were doing before kobold started this discussion. | 16:36 |
Theuni1 | ack | 16:36 |
*** __mac__ has quit IRC | 16:41 | |
*** romanofski has quit IRC | 17:10 | |
*** drudi has quit IRC | 17:11 | |
*** sweh has quit IRC | 17:16 | |
*** afd__ has quit IRC | 17:27 | |
*** afd__ has joined #zope3-dev | 17:33 | |
*** redir has joined #zope3-dev | 17:42 | |
*** projekt01 has joined #zope3-dev | 18:05 | |
*** afd__ has quit IRC | 18:26 | |
*** afd__ has joined #zope3-dev | 18:27 | |
*** sunoano has quit IRC | 18:27 | |
*** sunoano has joined #zope3-dev | 18:29 | |
*** ccomb has quit IRC | 18:36 | |
*** pcardune has joined #zope3-dev | 18:40 | |
*** kursor has joined #zope3-dev | 18:51 | |
*** redir has quit IRC | 18:52 | |
*** redir has joined #zope3-dev | 18:56 | |
*** redir has quit IRC | 19:07 | |
*** redir has joined #zope3-dev | 19:09 | |
*** pcardune has quit IRC | 19:11 | |
*** kursor has quit IRC | 19:16 | |
*** JaRoel|4D has quit IRC | 19:17 | |
*** JaRoel|4D has joined #zope3-dev | 19:19 | |
*** afd__ has quit IRC | 19:22 | |
*** projekt01 has quit IRC | 19:36 | |
*** pcardune has joined #zope3-dev | 19:49 | |
*** pcardune has quit IRC | 19:56 | |
*** alecm has joined #zope3-dev | 19:56 | |
*** alecm has quit IRC | 19:57 | |
*** aaronv has joined #zope3-dev | 20:04 | |
*** tarek has quit IRC | 20:05 | |
*** Theuni1 has quit IRC | 20:26 | |
*** tarek has joined #zope3-dev | 20:27 | |
*** drudi has joined #zope3-dev | 20:40 | |
*** junkafarian_ has joined #zope3-dev | 21:07 | |
*** sweh has joined #zope3-dev | 21:11 | |
*** kursor has joined #zope3-dev | 21:19 | |
*** drudi has quit IRC | 21:26 | |
*** pelle_ has joined #zope3-dev | 21:27 | |
*** pelle_ has quit IRC | 21:30 | |
*** pelle_ has joined #zope3-dev | 21:31 | |
*** sweh has quit IRC | 21:31 | |
*** malthe|away is now known as malthe | 21:51 | |
*** drudi has joined #zope3-dev | 22:07 | |
*** junkafarian_ has quit IRC | 22:09 | |
*** J1m has quit IRC | 22:10 | |
*** yota has quit IRC | 22:12 | |
*** redir has quit IRC | 22:14 | |
*** tarek_ has joined #zope3-dev | 22:14 | |
*** tarek has left #zope3-dev | 22:14 | |
*** tarek_ has quit IRC | 22:15 | |
*** tarek has joined #zope3-dev | 22:15 | |
*** pelle_ has quit IRC | 22:16 | |
*** kursor has quit IRC | 22:45 | |
*** alga has joined #zope3-dev | 23:02 | |
*** greenman has joined #zope3-dev | 23:53 |
Generated by irclog2html.py 2.15.1 by Marius Gedminas - find it at mg.pov.lt!