Lightest is a lightweight, task-oriented functional and integration testing framework. It is designed to jumpstart custom testing framework creation by removing common obstacles such as test syntax derivation, test execution management, and test result reporting. It stands on the shoulders of TestNG and built with the capable goodness of Groovy.
Lightest truly enables test automation on all scales, not just on the unit level. And it's so light, you'll forget it's even there.
This tutorial steps you through the process of installing Lightest, creating basic testing tasks and tests, and understanding the major benefits the framework has to offer. To get through this tutorial, you'll need to have working knowledge of Java and/or Groovy, or a similar language such as C#. Examples abound, so don't be afraid to dive right in!
(Note that some of the screenshots below may be outdated - even so, they should give an idea of what you would be seeing well enough!)
You can get a copy of Lightest from the Google Code project download page. The current version is 0.5
. The download archive contains the executable jar, license, and some documentation. Lightest is made available under the Apache 2.0 license.
Lightest requires a version of Java that supports annotations, and a recent version of Groovy. I'm currently running Java 1.5.0_16
and Groovy 1.5.6
; you should be fine if you have ballpark versions.
For this tutorial, simply unpack the distribution.
Lightest tests really are TestNG tests. To prove it, we'll begin by writing the simplest possible test, and running it with the Lightest test runner.
Tutorial.groovyimport com.googlecode.lightest.core.LightestTestCase import org.testng.annotations.* class Tutorial extends LightestTestCase { @Test void sayHello() { println "Hello World!" } }
Create an empty file called tutorial.config in the same directory as Tutorial.groovy .
We're ready for a test run. For simplicity's sake, I'll assume the Lightest core distribution jar is in the same folder along with the files we just created. You'll have to provide the path to the jar in your command line otherwise.
$ java -jar lightest-core-0.5-standalone.jar tutorial.config Tutorial.groovy
You should see some output like:
[Parser] Running: /private/tmp/lightest-suite33716.xml Hello World! =============================================== Suite1 Total tests run: 1, Failures: 0, Skips: 0 ===============================================
Alright - you've run your first Lightest test! Easy enough. Now let's see why Lightest is called a "task-oriented" testing framework. What are tasks?
Let's start by creating a task which prints "Hello World!" to the console, just as we did above using println
. Each task is written as its own Groovy class, and implements the ITask
interface. Most of the work is done by the task's perform()
method. To keep things nice and easy, we'll bootstrap our first task class by extending LightestTask
, which adds some default behaviors for other ITask
methods, and allows us to specify the task logic in a single doPerform()
method.
import com.googlecode.lightest.core.LightestTask import com.googlecode.lightest.core.ITaskResult class HelloWorld extends LightestTask { void doPerform(ITaskResult result) { println 'Hello World!' } }
Let's go ahead an whip up a quick test that uses our shiny new task!
Tutorial.groovyimport com.googlecode.lightest.core.LightestTestCase import com.googlecode.lightest.core.SimpleApi import org.testng.annotations.* class Tutorial extends LightestTestCase { Tutorial() { def api = new SimpleApi() setApi(api) } @Test void sayHello() { HelloWorld () } }
All Lightest tests extend LightestTestCase
. Since they are also TestNG tests, they make use of TestNG annotations, such as @Test
, which indicates that a given method should be considered a test method. To invoke the HelloWorld task, we simply provide the name of the task class - which is HelloWorld! Now let's actually give our greeting by running this test. There is one final piece of the puzzle before we can do that - the configuration file. Let's fill it in a bit.
config { classPaths { path ('/Users/chai/work/tutorial') } }
For now, we're putting all of our Groovy files in the same directory. I'm putting them in /Users/chai/work/tutorial . I specify this path in the configuration file. You might notice that this configuration file takes on the Groovy builder syntax, which allows concise specification of nested data structures. You'll be seeing more builder syntax later. For a complete configuration file reference, consult the Lightest documentation.
Ok, invoke the test runner again!
$ java -jar lightest-core-0.5-standalone.jar tutorial.config Tutorial.groovy
You will see similar output in the console, including our "Hello World!" greeting. And if you navigate to the lightest-report directory, you should see that some files were generated there. Open up lightest-report/index.html in a web browser, and you'll see an HTML report. Drill down into the suite, and mouse over the "HelloWorld" task. You should see it expand with details like this:
Tasks are the fundamental building blocks of Lightest tests. While in any given test you are free to do just anything Groovy allows you to, tasks bring the coherence and reportability to your tests that has traditionally been lacking in pure unit testing frameworks. Lightest enable you to easily create the tasks you want - but of course, it is up to you to design tasks appropriate for your application or system under test. Lightest will help where it can!
Congratulations!
Our HelloWorld task is great, but wouldn't it be even better if we could choose to give a slightly different greeting? After all, just saying "Hello World!" all the time might get a little boring. Let's parameterize the task! Modify it as follows (from here on in, import
statements will be omitted unless they are significant):
class HelloWorld extends LightestTask {
void doPerform(ITaskResult result) {
println config.'@greeting' ?: 'Hello World!'
}
}
If you run the task as you ran it before, the same result - "Hello World!" will be printed to the console. Modify your test to say a different greeting:
Tutorial.groovy
class Tutorial extends LightestTestCase {
Tutorial() {
def api = new SimpleApi()
setApi(api)
}
@Test
void sayHello() {
HelloWorld ()
}
@Test
void sayGreeting() {
HelloWorld (greeting: "Top of the mornin', world!")
}
}
Running the test, we get output like:
[Parser] Running: /private/tmp/lightest-suite36602.xml Top of the mornin', world! Hello World! =============================================== Suite1 Total tests run: 2, Failures: 0, Skips: 0 ===============================================
Now there's no way anyone can catch us boring the world with our salutations! In our task, we made use of the greeting
attribute of the config
object to pass along information from the test. The config
object is automatically available to any tasks that subclass LightestTask
. All tasks defined in this tutorial will do so for convenience. The attribute is accessed using the Groovy Node
attribute shorthand, namely the @
prefix. The ?:
Groovy operator is very useful here; if the greeting
attribute is null
, we say "Hello World!" by default.
In the test, we can choose to specify the greeting
value by using the Groovy Map
syntax within the parentheses following the task call. Can't really get simpler than that.
Since nobody knows just what we might say at a given time, wouldn't it be nice if there were a record of what we said after the fact? After all, it could have been pithy genius - and we might not ever grace the world with the same greeting ever again. Let's transcribe our remarks to the generated report. To do this, we only need to modify the task.
HelloWorld.groovyclass HelloWorld extends LightestTask { void doPerform(ITaskResult result) { def greeting = config.'@greeting' ?: 'Hello World!' println greeting result.setMessage("Said: ${greeting}") } }
We set the message on the result
object passed into the task's doPerform()
method to record what was actually said. Now if you take a look at the report, you'll find a few items of interest. First, there are now entries for both sayHello()
and sayGreeting()
. Separate TestNG test methods are reported independently. Second, the value that we used as the greeting in sayGreeting()
actually is displayed, despite my indicating otherwise - in the parameter list next to the name of the task. The parameter list for our original test sayHello()
is empty. However, if you mouse over the task, you will see that the message we printed is available there.
Let's take this opportunity to explore auto-wiring of properties. For task classes that extend LightestTask
, the values of attributes set on the config
object are automatically set to String
, int
, and boolean
properties with the same name. This is convenient because values do not need to be referenced from config
, which greatly improves the readability of the code. In the example below, we've turned greeting
into a String
property, with default visibility, which means that Groovy automatically creates getters and setters for us - which is what we want. We initialize it to the default value, "Hello World!". Then in doPerform()
, we can assume that if a value for greeting
was explicitly specified in the call to the task, that value will already have been assigned to greeting
:
class HelloWorld extends LightestTask {
String greeting = 'Hello World!'
void doPerform(ITaskResult result) {
println greeting
result.setMessage("Said: ${greeting}")
}
}
Note that two properties cannot be auto-wired, and must always be referenced from config
: name and value.
Next, we learn how to glean information about the context in which we're giving our salutations.
Each test runs in an environment, which potentially contains information about settings and resources specific to the test. For example, a test that needs to access the filesystem may need to know the "home" folder of the application-under-test. So far, we haven't specified any environment data, and our tests have not depended on it. Let's add a new task that queries the environment for ... THE WORLD. For now, our task will simply be informational:
QueryWorld.groovyclass QueryWorld extends LightestTask { void doPerform(ITaskResult result) { result.setMessage("Current World: ${env.getWorld()}") } }
The env
object is another special object made available by LightestTask
. It is set to the environment assigned to the running test. The environment itself has properties we will specify in a custom environment class that implements ITestEnvironment
:
import com.googlecode.lightest.core.TestEnvironment class TutorialEnvironment extends TestEnvironment { String world }
This Groovy class simply declares the elements that can be configured in the environment. It should implement ITestEnvironment
, which is taken care of by extending TestEnvironment
. We want the world to be specified in our environment, so we simply add another property called world
.
Now we actually have to provide the environment information. To do this, we return to the configuration file:
tutorial.config
config {
classPaths {
path ('/Users/chai/work/tutorial')
}
envs (class: 'TutorialEnvironment') {
env (id: 'default') {
world ('Earth')
}
}
}
We use the builder syntax again to declare envs
as a list of environments that are of our type, TutorialEnvironment
. Then we specify a "default" environment with its properties. With the environment in place, we should be able to use our newest task in a test:
class Tutorial extends LightestTestCase {
Tutorial() {
def api = new SimpleApi()
setApi(api)
}
@Test
void sayHello() {
HelloWorld ()
}
@Test
void sayGreeting() {
QueryWorld ()
HelloWorld (greeting: "Top of the mornin', world!")
}
Run the test, and take a look at the report. You should see an entry for the QueryWorld
task, reporting a message of "Current World: Earth". No place like home!
A final note before moving on to the next topic: preferences are another configurable entity, like environments. However, while there may be multiple environments for a given test run, these is only one set of preferences across the run. The idea behind having multiple environments is that you may want to have your integration tests run in parallel across multiple enviroments, for faster feedback. However, the preferences are intended to be cross-cutting - for example a timeout interval. Just as our custom environment implemented the ITestEnvironment
interface, custom preferences implement IPreferences
, which is a marker interface only. Here's a peek at how you might specify preferences in your config file:
config { ... prefs (class: 'MyCustomPreferencesClass') { timeout (30) speed (100) } ... }
So far we've been executing single tasks, and in the last section, two tasks, in the sayGreeting()
test. Let's explore one of the cooler features of Lightest - task nesting. Sometimes you might have a group of tasks related to a shared goal. If any of the tasks fail, the goal cannot be achieved. By the same token, if the tasks succeed, it would be convenient to represent them as a single lineitem rather than a series of distinct tasks.
We revisit the Groovy builder syntax. In Lightest tests, nested tasks can be specified in the same way that, for example, the configuration file specifies hierarchically organized values. With nested tasks, you have top level tasks, their children, and the children of their children - turtles all the way down. When all child tasks of a given parent task succeed (and the parent task itself is successful), the parent task is considered to have passed. If any child tasks fail, then the parent task is considered to have failed. If a parent task fails, the child tasks, which are assumed to have a dependency on the parent task, will not even be performed. We'll explore task failures further in the next section.
Greeting the world requires there to be a world. Let's modify our test a bit (from here on in, code samples will focus just on the test in question):
Tutorial.groovy@Test void sayGreeting() { QueryWorld () { HelloWorld (greeting: "Top of the mornin', world!") } }
Crack open the report and view the results for sayGreeting()
. When you mouse over the QueryWorld
task, it will expand. Notice one of the boxes in the upper-right corner of the task result, which will have one of the following values: 一, 二, or 三. (These may not display correctly on your system unless you have chosen to "Install files for East Asian languages".) The default should be 二. This refers to the display level of the task result, which you can toggle by clicking the header of the result - the line containing the task name and the task parameters. Click the header now. You should see the HelloWorld
task result expand below the QueryWorld
one, indented to indicate it is a child task. The task display level will now be 三. Click it again to cycle to 一, at which point the child result will hide itself again.
You can use various control constructs within Groovy builder closures, for example if
statements and for
loops. We could do something nifty like this, to showcase our mastery over greetings across regions and tongues:
static final GREETINGS = [ "Top of the mornin', world!" , "Howdy, world!" , "G'day mate, world!" , "Bonjour, world!" ] @Test void sayGreeting() { QueryWorld (description: 'Find a world, and greet it internationally') { for (g in GREETINGS) { HelloWorld (greeting: g) } } }
When you run this test, you will see the following console output:
Top of the mornin', world! Howdy, world! G'day mate, world! Bonjour, world!
And when you drill down into the QueryWorld
task result, setting the task display level to 三 as before, you will find 4 HelloWorld
child tasks. The important thing to note is that all of our tasks thus far has been successful. It is very nice that the child tasks roll up and are hidden with 一 and 二. The special description
value set to the top level task gives us enough of an idea of what the job of the entire "task tree" is, and is easily viewable in the QueryWorld
task result. Since all tasks passed, the details are hidden from us by default, and we have little reason to worry about them.
Another cool aspect of nested tasks is that they are able to obtain information from their parent tasks, and their associated results. In the following example, the HelloWorld
task checks to see if it has a parent QueryWorld
task, and if so adds the message from the parent result to the current result. The ?.
safe dereferencing Groovy operator is used heavily to avoid NullPointerException
's for tasks that do not have a parent.
class HelloWorld extends LightestTask {
String greeting = 'Hello World!'
void doPerform(ITaskResult result) {
def parentResult = result.parent
if (parentResult?.getTask()?.class?.name == 'QueryWorld') {
greeting += " (${parentResult.getMessage()})"
}
println greeting
result.setMessage("Said: ${greeting}")
}
}
The message for the nested HelloWorld
tasks should now be reported as something like:
Said: Howdy, world! (Current World: Earth)
While the current example seems fairly trivial (indeed, does it make sense to echo a message that is already available in the report?), it does illustrate that the result hierarchy can be easily navigated. This can be used to good effect when child tasks need to modify their behavior based on the configuration and/or result of their parent tasks.
So far things are going just peachy. We know how to write tasks, and provide information about the task run in the resulting report. We know how to get information about the environment, and to group related tasks via nesting. But now - you hear the ominous peal of thunder! In the next section, we'll experience stormier times...
In a way, tests are designed to fail. And the best tests give us lots of useful information when they fail. In this section, we explore task failures, especially failures in nested tasks. Let's have some fun!
WorldNotFoundException.groovyclass WorldNotFoundException extends Exception { WorldNotFoundException(String message) { super(message) } }
Evil empires are known to say, "we'll blow your PLANET up!"
QueryWorld.groovy
class QueryWorld extends LightestTask {
void doPerform(ITaskResult result) {
def world = env.getWorld()
assert world != null
if ('Alderaan'.equalsIgnoreCase(env.getWorld())) {
throw new WorldNotFoundException("World not found: ${world}")
}
result.setMessage("Current World: ${env.getWorld()}")
}
}
And the tweak to set the plan in motion:
tutorial.config
config {
classPaths {
path ('/Users/chai/work/tutorial')
}
envs (class: 'TutorialEnvironment') {
env (id: 'default') {
world ('Alderaan')
}
}
}
Run the test! Now that's an unfamiliar sight in the console:
===============================================
Suite1
Total tests run: 2, Failures: 1, Skips: 0
===============================================
Check the report. You'll find that our sayGreeting()
test from the above section is highlighted in red. If you mouse over the QueryWorld
task, you will see the message is "Unexpected exception: World not found: Alderaan", and the WorldNotFoundException
stack trace is shown as the response data. And our nested HelloWorld
task? It's nowhere to be found, even if you change the task display level. That's because when parent tasks fail, their child tasks are not even performed.
However, all is not lost. Unlike typical unit testing frameworks, the task-oriented nature of Lightest means the test may continue despite failures. See that in action by modifying the test to add a post-failure task:
Tutorial.groovy
@Test
void sayGreeting() {
QueryWorld (description: 'Find a world, and greet it internationally') {
for (g in GREETINGS) {
HelloWorld (greeting: g)
}
}
HelloWorld (greeting: 'Hello, non-blown-up world!')
}
The test is still considered a failure; however you can see that the non-failing HelloWorld
task was still performed, and indicated success. This is because it does not have a parent-child relationship with the failing task.
What if the task that failed was the child task, not the parent? Let's add a new test called sayHelloBeforeChecking()
:
@Test void sayHelloBeforeChecking() { HelloWorld (greeting: 'Hello, non-blown-up world!') { QueryWorld (description: 'Oops, spoke too soon!') HelloWorld (greeting: 'Hello, Smith Areans!') } }
Ah ha, when a child task fails, the parent fails too - but sibling tasks are still executed and may succeed. In this case, you have to click on the parent HelloWorld
task for sayHelloBeforeChecking()
to get the passing child HelloWorld
task to display. This is because of the "smart" hiding of passing child tasks discussed in the previous section.
We have just observed task failures due to exceptions being thrown by code within the task - the exception is caught and handled by the LightestTask
superclass. Not all failures are caused by exceptions, however. Tasks (assertion tasks, for example) may check for conditions that, if unmet, will indicate the task has failed. They can do this by setting the status of the result
object. Here we modify the QueryWorld
task to not throw an exception:
class QueryWorld extends LightestTask { void doPerform(ITaskResult result) { def world = env.getWorld() assert world != null if ('Alderaan'.equalsIgnoreCase(env.getWorld())) { //throw new WorldNotFoundException("World not found: ${world}") result.setMessage("World not found: ${world}") result.fail() } else { result.setMessage("Current World: ${env.getWorld()}") } } }
The task now fails without a stacktrace. It is key to indicate any available information about the failure using setMessage()
, to aid in debugging.
The standard task result status when one is not set explicitly indicates success - that's why the earlier task implementations in this tutorial did not specify a status. Apart from fail()
'ing the task, two other options are available to indicate anything but complete success: flag()
and doom()
. All three of these options result in failure for the containing test.
flag()
should be used to indicate a potential error condition when performing a task. The intent is for the troubleshooter to see the failed test, drill down into its tasks to see the flagged task, and determine whether or not there is a real issue. doom()
should be used when little or no value will be gained from continuing to execute the test; all tasks that have not yet been performed will be abandoned. This is similar to the behavior of typical unit testing frameworks upon encountering an exception.
Experiment and have fun with the following example! Probably the most important thing to note is that any tasks following a doom()
'ed task do not get executed.
class QueryWorld extends LightestTask {
void doPerform(ITaskResult result) {
def world = env.getWorld()
assert world != null
if ('Alderaan'.equalsIgnoreCase(env.getWorld())) {
//throw new WorldNotFoundException("World not found: ${world}")
result.setMessage("World not found: ${world}")
result.fail()
}
else if ('Atlantis'.equalsIgnoreCase(env.getWorld())) {
result.setMessage("World quite difficult to find: ${world}")
result.flag()
}
else if ('Hades'.equalsIgnoreCase(env.getWorld())) {
result.setMessage("Unable to find more worlds: ${world}")
result.doom()
}
else {
result.setMessage("Current World: ${env.getWorld()}")
}
}
}
TestNG is still hard at work behind the scenes, and this is evident when tests fail. In the reporting folder you will find a file called testng-failed.xml, which not only lists the failures as XML, but also allows you to easily rerun only the failed tests, with a command such as the following:
$ java -jar lightest-core-0.5-standalone.jar tutorial.config testng-failed.xml
Although you may specify multiple .groovy files to execute as tests when running Lightest, using TestNG's XML suite files gives much more flexibility. Consult the TestNG documentation for more information on the suite XML syntax.
Tasks are building blocks. It makes sense that you should be able to compose individual tasks into higher-level tasks. For example, if we're always going to query for a world before saying "hello" to it, we shouldn't have to repeat this pattern every time. With Lightest, task composition is a breeze. Let's create a new task called GreetWorld
, essentially copying our sayGreeting()
test:
import com.googlecode.lightest.core.ITaskResult import com.googlecode.lightest.core.LightestTask class GreetWorld extends LightestTask { static final GREETINGS = [ "Top of the mornin', world!" , "Howdy, world!" , "G'day mate, world!" , "Bonjour, world!" ] void doPerform(ITaskResult result) { QueryWorld (description: 'Find a world, and greet it internationally') { for (g in GREETINGS) { HelloWorld (greeting: g) } } HelloWorld (greeting: 'Hello, non-blown-up world!') } }
That's all it takes! We can invoke our tasks directly in the doPerform()
method of new task definitions. This is possible through more Groovy meta-programming magic.
Let's create one more new task to keep things interesting:
GoodbyeWorld.groovyimport com.googlecode.lightest.core.LightestTask import com.googlecode.lightest.core.ITaskResult class GoodbyeWorld extends LightestTask { void doPerform(ITaskResult result) { def lastWords = (config.'@lastWords' ? "So long, and ${config.'@lastWords'}!" : 'Sayonara, sucker!') println lastWords result.setMessage("*waved* and said: ${lastWords}") } }
Tasks that are composed of other tasks can be used in exactly the same way as non-composed tasks in a testcase. Information on all component tasks is available in the test report, and will be folded under the containing task by default. The behavior of task nesting, however, does deserve some added attention. Consider the following test:
Tutorial.groovy
@Test
void helloGoodbye() {
GreetWorld () {
GoodbyeWorld (lastWords: 'thanks for all the fish')
}
}
Here, we are saying that the GoodbyeWorld
task depends on the GreetWorld
task. When you run this test, you will see GreetWorld
's component tasks QueryWorld
and HelloWorld
, followed by the GoodbyeWorld
task, all at the first nesting level under GreetWorld
. You will have to expand the task result by clicking on the header to see the nested results. Let's get down to earth before running it:
env (id: 'default') {
world ('Earth')
}
Verify the task nesting in the report - then we're back to deep space!
tutorial.config
env (id: 'default') {
world ('Alderaan')
}
The QueryWorld
component task fails, causing the GreetWorld
task that houses it to fail. Then, because GoodbyeWorld
task depends on GreetWorld
, which has failed, it is not even performed. However, according to normal nesting rules, the HelloWorld
is still performed, and we can see that it is successful.
Composition of tasks is a fantastic way to make tests both more expressive and concise. And Lightest makes it a piece of cake!
The initial test scope for any test should be neutral, a clean slate so to speak. However, it's quite likely that as a test is run, context is added to that scope, and that tasks will want to share that contextual information. For example, if one task starts a sessioned application, follow-on tasks may want to know the session id.
We could easily keep a variable in the test, and pass it to each task:
Tutorial.groovy@Test void helloGoodbye() { def words = 'It was the best of times, it was the worst of times...' HelloWorld (greeting: words) GoodbyeWorld (lastWords: words) }
There's another way to accomplish the same, without explicitly passing values to the tasks every time - by adding the values to the context
! We can modify the tasks to use context values if they are available:
void doPerform(ITaskResult result) {
def greeting = config.'@greeting' ?: 'Hello World!'
...
context.sharedWords = greeting
}
GoodbyeWorld.groovy
void doPerform(ITaskResult result) {
def lastWords = context.sharedWords ?: config.'@lastWords'
lastWords = lastWords ? "So long, and ${lastWords}!" : 'Sayonara, sucker!')
...
}
With tasks defined as above, GoodbyeWorld
could be invoked without passing the lastWords
parameter, and would still have knowledge of how the world was greeted by HelloWorld
.
Context values stay around for the life of a test method, and are cleared afterwards. The are available in the main body of the test, and in any of its tasks. Hopefully this feature will make it easier for your tasks to share context with one another!
Ever want to pause a running test because you notice something spooky going on? Or have to comment-out parts of it and re-run in order to reproduce the conditions under which it failed? About as fun as soaking in a knife bath.
Lightest offers several ways to interactively step through running tests which essentially boil down to test debugging. This is most easily demonstrated by starting the test runner in interactive mode, with the --interactive
switch. Let's use our version of helloGoodbye()
from a couple of sections back:
@Test void helloGoodbye() { GreetWorld () { GoodbyeWorld (lastWords: 'thanks for all the fish') } }
And ... go!
$ java -jar lightest-core-0.5-standalone.jar --interactive tutorial.config Tutorial.groovy [Parser] Running: /tmp/lightest-suite10161.xml [entering interactive mode] Current task: "Find a world, and greet it internationally" QueryWorld {} ... at helloGoodbye(), line 25 OK, "Current World: Earth" >>>
In interactive mode, the next task is always run, and its results displayed, before a command is solicited from the user. We find the description of the task, the name of the task, its parameters (none provided for QueryWorld
here), the line number and method name of the current position in the test file, the result status (OK
), and the result message, all presented above the prompt. That's useful information! Note - the line number is accurate only for top-level tasks.
You can always type help
to get a listing of available commands:
>>> help [help] You are currently in interactive mode. You may step through test execution task by task and view the results of tasks as they are performed. Available commands: a, again Try the current task again. c, crawl Move to the next task, descending into child tasks (if any). w, walk Move to the next top-level task, skipping all child tasks. r, run Exit interactive mode and resume normal execution of tasks, until either a failure or breakpoint is encountered. f, fly Exit interactive mode and resume normal execution of tasks. Don't stop for anything less than user input. h, help Show this help message. Current task: "Find a world, and greet it internationally" QueryWorld3 {} ... at helloGoodbye(), line 25 OK, "Current World: Earth" >>>
The help text is pretty self-explanatory - choose your mode of travel, depending on how quickly you want to step through the test. Let's try crawling first:
>>> c [crawl] Top of the mornin', world! Current task: "" HelloWorld {greeting=Top of the mornin', world!} ... at helloGoodbye(), line 25 OK, "Said: Top of the mornin', world!" >>> c [crawl] Howdy, world! Current task: "" HelloWorld {greeting=Howdy, world!} ... at helloGoodbye(), line 25 OK, "Said: Howdy, world!" >>> c [crawl] G'day mate, world! Current task: "" HelloWorld {greeting=G'day mate, world!} ... at helloGoodbye(), line 25 OK, "Said: G'day mate, world!" >>> c [crawl] Bonjour, world! Current task: "" HelloWorld {greeting=Bonjour, world!} ... at helloGoodbye(), line 25 OK, "Said: Bonjour, world!" >>>
Hmm, a little slow. Let's walk:
>>> w [walk] Hello, non-blown-up world! Current task: "" GreetWorld {} ... at helloGoodbye(), line 25 OK, "" >>> w [walk] So long, and thanks for all the fish! =============================================== Suite1 Total tests run: 1, Failures: 0, Skips: 0 =============================================== $
What, the test is over, and we didn't even see the GoodbyeWorld
task!? This is because walk
ing always steps through the top level tasks, and steps over child tasks. We can change that by setting a breakpoint in our test, however! The breakpoint parameter is available to all tasks that subclass LightestTask
.
@Test
void helloGoodbye() {
GreetWorld () {
GoodbyeWorld (lastWords: 'thanks for all the fish', breakpoint: true)
}
}
Ok, here we go:
[entering interactive mode]
Current task: "Find a world, and greet it internationally"
QueryWorld3 {} ... at helloGoodbye(), line 25
OK, "Current World: Earth"
>>> w
[walk]
Top of the mornin', world!
Howdy, world!
G'day mate, world!
Bonjour, world!
Hello, non-blown-up world!
Current task: ""
GreetWorld {} ... at helloGoodbye(), line 25
OK, ""
>>> w
[walk]
So long, and thanks for all the fish!
[breakpoint]
Current task: ""
GoodbyeWorld {lastWords=thanks for all the fish} ... at helloGoodbye(), line 25
OK, "*waved* and said: So long, and thanks for all the fish!"
>>>
Ah ha, now we have a chance to inspect the situation around the execution of the GoodbyeWorld
task! Setting breakpoints on tasks is a convenient way to stop the test at a given point. Remember that for the breakpoint to take effect, you must be in interactive mode. Once in interactive mode, you can run
to the next breakpoint if you don't want to stop and smell the roses along the way.
To segue into the next section, let me demonstrate how you can jump into interactive mode even when the test runner was started normally.
Tutorial.groovy
@Test(invocationCount = 50, threadPoolSize = 1)
void helloGoodbye() {
GreetWorld () {
GoodbyeWorld (lastWords: 'thanks for all the fish', breakpoint: true)
}
}
We added TestNG annotation parameters to indicate the the test should run, not once, but fifty times. A thread pool size of 1 indicates that all tests should be run sequentially in a single thread. This is important, because interactive mode cannot be entered during multi-threaded test runs. (Ok, that's a white lie - you might be able to enter interactive mode, but it won't be pretty!)
To enter interactive mode while the test is running, start the test without the --interactive
switch, and hit the Enter key any time when the test is running. You'll pop into interactive mode! From here, you can do anything you would normally do in interactive mode.
Top of the mornin', world!
Howdy, world!
G'day mate, world!
Bonjour, world!
Hello, non-blown-up world!
So long, and thanks for all the fish!
Top of the mornin', world!
Howdy, world!
G'day mate, world!
Bonjour, world!
Hello, non-blown-up world!
So long, and thanks for all the fish!
Top of the mornin', world!
Howdy, world!
G'day mate, world!
Bonjour, world!
Hello, non-blown-up world!
So long, and thanks for all the fish!
Top of the mornin', world!
Howdy, world!
G'day mate, world!
Bonjour, world!
Hello, non-blown-up world!
So long, and thanks for all the fish!
[entering interactive mode]
Current task: "Find a world, and greet it internationally"
QueryWorld3 {} ... at helloGoodbye(), line 25
OK, "Current World: Earth"
>>> yay! <--- much rejoicing
How about running your tests in parallel? Sounds great, right? - running everything simultaneously, everything should finish in better time! Not quite ...
All tests need environments to run in. What's more, each environment should not have more than one test mucking around in it at a time - that's a recipe for surprising (and useless!) results. Therefore, if we want to run tests in parallel, we need to guarantee a unique environment for each test, for the duration of that test.
In Lightest, environments are specified in the configuration file. Whereas in the examples above we have been dealing with a single environment, we can easily specify multiple environments, so long as their associated resources do not conflict. This last point is important - if the environment involves resources such as executables or the filesystem, it should not share them with any other environment. In our trivial tutorial example we don't have to worry about this:
tutorial.configenvs (class: 'TutorialEnvironment') { env (id: 'default') { world ('Earth') } env (id: 'retreat') { world ('Moon') } env (id: 'last stand') { world ('Mars') } }
With this configuration, we may run a maximum of three tests concurrently. In fact, this is the magic number of concurrent tests allowed by default when you do not explicitly define environments in the configuration file. If there are more concurrent test threads than defined environments, all test threads will block until environments in which they may run are freed up.
There are several ways to run tests concurrently in TestNG, specifying the number of threads in the suite XML probably being the easiest:
testng.xml
<suite name='Suite1' parallel='methods' thread-count='5'>
<test name='Test1'>
<classes>
<class name='Tutorial' />
</classes>
</test>
</suite>
Then running:
$ java -jar lightest-core-0.5-standalone.jar tutorial.config testng.xml
Note that while test methods may be running in their own threads, Lightest requires that all test methods within a given testcase class be run using the same test environment. Methods from different classes, however, may of course run with different environments.
The Lightest report displays the environment used by each test (the default
environment here):
Work has been done to make test concurrency stable in Lightest. However, if you encounter flakiness, please do file a bug in the issue tracker! But don't all get up at once!
"All this is well and good", you say, "but anyone can write a test framework that simply prints 'Hello World!' !" And you'd be correct! For the final part of this tutorial, we tour a sample implementation of a Selenium web test using Lightest. Our goal is to create a test that verifies the Lightest project page is ranked among the top search results, using several popular search engines.
We start by downloading the Selenium RC, configuring our environment, and bringing up the Selenium server instance. The server will be part of our test environment, so we define properties associated with it.
SeleniumEnvironment.groovy
import com.googlecode.lightest.core.TestEnvironment
class SeleniumEnvironment extends TestEnvironment {
String seleniumServerHost
int seleniumServerPort
String defaultBrowserStartCommand
}
Copy the Selenium Java client driver JAR to some location which you can reference from your configuration file. We'll need it to be on the class path so our tasks can use its classes.
tutorial.configconfig { classPaths { path ('/home/chai/work/lightest/core/tutorial/selenium-java-client-driver.jar') } envs (class: 'SeleniumEnvironment') { env (id: 'default') { seleniumServerHost ('localhost') seleniumServerPort (4444) defaultBrowserStartCommand ('*firefox') } } }
Now start up the Selenium server. We'll keep it running while we build the rest of the test just so it's available.
$ java -jar selenium-server.jar -singleWindow
13:11:57.426 INFO - Java: Sun Microsystems Inc. 1.6.0_0-b11
13:11:57.427 INFO - OS: Linux 2.6.24-22-generic i386
13:11:57.429 INFO - v1.0-SNAPSHOT [1123], with Core v@VERSION@ [@REVISION@]
13:11:57.530 INFO - Version Jetty/5.1.x
13:11:57.531 INFO - Started HttpContext[/selenium-server/driver,/selenium-server/driver]
13:11:57.533 INFO - Started HttpContext[/selenium-server,/selenium-server]
13:11:57.533 INFO - Started HttpContext[/,/]
13:11:57.540 INFO - Started SocketListener on <IP_ADDRESS>:4444
13:11:57.543 INFO - Started org.mortbay.jetty.Server@1d2068d
Let's make some generic Selenium tasks. We want one to create a Selenium instance, execute a Selenium command, and close the Selenium instance. We'll use the HttpCommandProcessor
class instead of DefaultSelenium
so that the core Selenium verification methods are available to us.
import com.googlecode.lightest.core.LightestTask import com.googlecode.lightest.core.ITaskResult import com.thoughtworks.selenium.HttpCommandProcessor /** * Starts a Selenium instance, and attaches it to the context as a property * called "selenium". An alternate label can be provided for case when multiple * Selenium instances are used at the same time. */ class OpenSelenium extends LightestTask { /** * Environment: * seleniumServerHost * seleniumServerPort * defaultBrowserStartCommand * * Requires: * browserStartCommand * browserURL * * Optional: * browserStartCommand (defaults to env.defaultBrowserStartCommand) * label (defaults to "selenium") * */ void doPerform(ITaskResult result) { assert env.seleniumServerHost != null assert env.seleniumServerPort != null assert config.'@browserURL' != null def browserStartCommand = (config.'@browserStartCommand' ?: env.defaultBrowserStartCommand) def label = config.'@label' ?: 'selenium' def selenium = new HttpCommandProcessor( env.seleniumServerHost, env.seleniumServerPort, browserStartCommand, config.'@browserURL') selenium.start() result.setMessage('Successfully opened browser') // close an existing labeled instance, if any if (context[label] instanceof HttpCommandProcessor) { context[label].stop() } context[label] = selenium } }DoSelenium.groovy
import com.googlecode.lightest.core.LightestTask import com.googlecode.lightest.core.ITaskResult class DoSelenium extends LightestTask { /** * Requires: * command * target * * Optional: * value (defaults to "") * label (defaults to "selenium") */ void doPerform(ITaskResult result) { assert config.'@command' != null assert config.'@target' != null def command = config.'@command' def target = config.'@target' def value = config.'@value' ?: "" def label = config.'@label' ?: 'selenium' def args = (String[]) [ target, value ] result.setMessage(context[label].doCommand(command, args)) } }CloseSelenium.groovy
import com.googlecode.lightest.core.LightestTask
import com.googlecode.lightest.core.ITaskResult
import com.thoughtworks.selenium.HttpCommandProcessor
class CloseSelenium extends LightestTask {
/**
* Optional:
* label (defaults to "selenium")
*/
void doPerform(ITaskResult result) {
def label = config.'@label' ?: 'selenium'
if (context[label] instanceof HttpCommandProcessor) {
context[label].stop()
}
context[label] = null
}
}
We said we wanted to be able to test multiple search engines. Let's create a simplistic interface representing a web search engine as viewed by Selenium, and create a few implementations.
SearchEngine.groovyinterface SearchEngine { /** * Returns the URL String where the search can be initiated. */ String getUrl() /** * Returns the Selenium locator for the search box, which can be typed * into. */ String getSearchLocator() /** * Returns the Selenium locator for the search submission element, which * can be clicked. */ String getSubmitLocator() /** * Returns the Selenium locator for all entries on the search result page * whose links point to a URL that starts with the given String. * * @param startsWithURL the URL the result links should start with */ String getResultEntryLocator(String startsWithURL) }
Here come the real engines! You can see that these "implementations" are essentially configurations, with no significant variations in code. We use the convenient Groovy feature of automatically adding getters for defined properties to implement most get*()
methods of the interface.
class GoogleSearch implements SearchEngine { String url = 'http://www.google.com' String searchLocator = 'name=q' String submitLocator = 'name=btnG' String getResultEntryLocator(String startsWithURL) { return "xpath=id('res')/descendant::a[starts-with(@href, '${startsWithURL}')]" } }LiveSearch.groovy
class LiveSearch implements SearchEngine { String url = 'http://www.live.com' String searchLocator = 'name=q' String submitLocator = 'name=go' String getResultEntryLocator(String startsWithURL) { return "xpath=id('results')/descendant::a[starts-with(@href, '${startsWithURL}')]" } }YahooSearch.groovy
class YahooSearch implements SearchEngine { String url = 'http://www.yahoo.com' String searchLocator = 'id=p' String submitLocator = 'id=searchsubmit' String getResultEntryLocator(String startsWithURL) { return "xpath=id('web')/descendant::a[starts-with(@href, '${startsWithURL}')]" } }CuilSearch.groovy
class CuilSearch implements SearchEngine { String url = 'http://www.cuil.com' String searchLocator = 'name=q' String submitLocator = "xpath=/descendant::button[@title='Search']" String getResultEntryLocator(String startsWithURL) { return "xpath=/descendant::div[@class='result']/descendant::a[starts-with(@href, '${startsWithURL}')]" } }
Let's take a stab at writing this test.
SeleniumTutorial.groovyimport com.googlecode.lightest.core.LightestTestCase import com.googlecode.lightest.core.SimpleApi import org.testng.annotations.* class SeleniumTutorial extends LightestTestCase { SeleniumTutorial() { def api = new SimpleApi() setApi(api) } @Test void lightestProjectPageIsRanked() { def engines = [ new GoogleSearch(), new LiveSearch(), new YahooSearch(), new CuilSearch() ] for (engine in engines) { OpenSelenium (browserURL: engine.getUrl()) DoSelenium (command: 'open', target: '/') DoSelenium (command: 'type', target: engine.getSearchLocator(), value: 'lightest test') DoSelenium (command: 'clickAndWait', target: engine.getSubmitLocator()) DoSelenium (command: 'verifyElementPresent', target: engine.getResultEntryLocator('http://code.google.com/p/lightest')) CloseSelenium () } } }
Run it:
$ java -jar lightest-core-0.5-standalone.jar tutorial.config SeleniumTutorial.groovy
This actually works! And we get a nice report detailing each Selenium step! Let's see if we can do even better. We want to quickly be able to scan the report for each search engine. The results for the above test for each of the various search engines are a bit tedious to analyze. And simply copy-pasting to more @Test
's would violate the DRY principle.
We define a new composite task called AssertResultRanked
that operates upon a provided search engine.
import com.googlecode.lightest.core.LightestTask import com.googlecode.lightest.core.ITaskResult /** * Asserts that a given site is ranked on the first results page for a given * search term and search engine. Opens a new Selenium instance for this * purpose, and closes it when done. */ class AssertResultRanked extends LightestTask { /** * Requires: * searchTerm * siteURL * * Optional: * searchEngine (defaults to instance of GoogleSearch) */ void doPerform(ITaskResult result) { assert config.'@searchTerm' != null assert config.'@siteURL' != null def searchTerm = config.'@searchTerm' def siteURL = config.'@siteURL' SearchEngine engine = config.'@searchEngine' ?: new GoogleSearch() OpenSelenium (browserURL: engine.getUrl()) DoSelenium (command: 'open', target: '/') DoSelenium (command: 'type', target: engine.getSearchLocator(), value: searchTerm) DoSelenium (command: 'clickAndWait', target: engine.getSubmitLocator()) DoSelenium (command: 'verifyElementPresent', target: engine.getResultEntryLocator(siteURL)) CloseSelenium () } }
Now our test across all search engines is much simplified:
SeleniumTutorial.groovy@Test void lightestProjectPageIsRanked() { def engines = [ new GoogleSearch(), new LiveSearch(), new YahooSearch(), new CuilSearch() ] for (engine in engines) { AssertResultRanked (searchTerm: 'lightest test', siteURL: 'http://code.google.com/p/lightest', searchEngine: engine) } } }
A top level result in the report is now associated with each search engine. It's easy to see which ones failed our ranking test! (Maybe this will change ... with time?)
The above is just one of the many ways you might create a test framework using Selenium and Lightest. I hope this simple example serves to get the juices flowing! Here I will present an alternative approach to writing the same test, this time making it possible to use Selenium in way you would outside of the Lightest framework. This section is considerably more advanced than the previous; feel free to skip it for now.
The idea is to allow Selenium commands to be invoked directly off an object, instead of via tasks:
selenium.click('btnG')
But we still want tasks to be created, for reporting purposes, so that we can put the test in interactive mode, and to enjoy the other benefits of using Lightest. For this, we will reuse the three tasks OpenSelenium
, DoSelenium
, and CloseSelenium
defined above. We will extend the HttpCommandProcessor
so that, instead of executing commands directly, commands are "intercepted", so to speak, and routed through these tasks. The command processor created by OpenSelenium
will be the one that actually does the heavy lifting. Then we will wrap the DefaultSelenium
object so that it plays nicely with closures, which are integral to the builders at the core of Lightest.
import com.googlecode.lightest.core.ITaskProvider import com.googlecode.lightest.core.LightestTestCase import com.googlecode.lightest.core.LightestTask import com.thoughtworks.selenium.HttpCommandProcessor import com.googlecode.lightest.core.LightestContext /** * Produces Lightest tasks that send commands to the server, instead of doing * so directly. This example implementation borrows most functionality from the * parent class for convenience, and DOES NOT consistently override all * inherited methods. * * setContext() must be called before any of start(), doCommand(), or stop() * to set the object that is used to fetch the task provider for creating the * API tasks OpenSelenium, DoSelenium, and CloseSelenium, respectively. */ public class TaskBackedCommandProcessor extends HttpCommandProcessor { LightestContext context private browserStartCommand private browserURL /** * @param browserStartCommand this value is fed to the OpenSelenium task. * Even if null, the task should obtain an * appropriate default from the environment. * @param browserURL */ TaskBackedCommandProcessor(String browserStartCommand, String browserURL) { // we never actually invoke the superclass' doCommand() super('localhost', 4444, browserStartCommand, browserURL) this.browserStartCommand = browserStartCommand this.browserURL = browserURL } @Override String doCommand(String command, String[] args) { def target = (args.length > 0) ? args[0] : "" def value = (args.length > 1) ? args[1] : "" def result = getTaskProvider().DoSelenium (command: command, target: target, value: value) return result.getMessage() } @Override void start() { getTaskProvider().OpenSelenium ( browserStartCommand: browserStartCommand, browserURL: browserURL) } @Override void stop() { getTaskProvider().CloseSelenium () } private ITaskProvider getTaskProvider() { assert context != null return context.getTaskProvider() } }
Within the scope of a testcase or a task, tasks can normally be invoked simply by specifying the task name. This is because tests and tasks are considered task providers, and specifically implement the ITaskProvider
interface. In other scopes, we need to obtain an ITaskProvider
from which to invoke tasks. This can be obtained from a LightestContext
object, which must be set on the TaskBackedCommandProcessor
before key methods are called (as the highlighted documentation indicates).
Once we have the task provider, we can invoke tasks off of it directly. Note that the task provider is queried for each task invocation via getTaskProvider()
, and is not cached. The reason for this is that the currently appropriate task provider may change through the course of the test. The LightestContext
object is made aware of all such changes, and should always be consulted for the current task provider.
Next, we wrap the Selenium object for special behaviors:
TaskBackedSelenium.groovy
import com.googlecode.lightest.core.LightestContext
import com.googlecode.lightest.core.TaskNodeBuilder
import com.thoughtworks.selenium.DefaultSelenium
import com.thoughtworks.selenium.GroovySelenium
import com.thoughtworks.selenium.Selenium
/**
* This extension of the GroovySelenium wrapper object is specialized for
* Lightest, and will not choke when used inside a TaskNodeBuilder closure. Its
* command processor, a TaskBackedCommandProcessor, produces tasks for running
* Selenium commands.
*
* setContext() should be called to set the context used both to determine the
* closure state, and for the underlying command processor to build tasks.
*/
class TaskBackedSelenium extends GroovySelenium {
private TaskBackedCommandProcessor commandProcessor
private LightestContext context
TaskBackedSelenium(String browserStartCommand, String browserURL) {
this(new TaskBackedCommandProcessor(browserStartCommand, browserURL))
}
TaskBackedSelenium(TaskBackedCommandProcessor commandProcessor) {
this(new DefaultSelenium(commandProcessor))
this.commandProcessor = commandProcessor
}
private TaskBackedSelenium(Selenium selenium) {
super(selenium)
}
void setContext(LightestContext context) {
this.context = context
commandProcessor.setContext(context)
}
/**
* Swallows exceptions that originate when the method is invoked inside a
* TaskNodeBuilder closure. The underlying command processor will not yield
* return values for such calls, which may cause its calling methods to
* fail.
*/
@Override
def methodMissing(String name, args) {
try {
return super.methodMissing(name, args)
}
catch (e) {
def taskProvider = context.getTaskProvider()
if (! (taskProvider instanceof TaskNodeBuilder)) {
throw e
}
}
}
}
The GroovySelenium
object wraps a Selenium
implementation, and adds some convenient features, such as the ability to merge waitForPageToLoad()
calls into normal methods invoked on the Selenium object. But here we take advantage of the fact that it routes all Selenium API calls through methodMissing()
to catch exceptions related to the conflict of imposing task-orientation on the Selenium implementation.
What you say?! I'll explain after finishing up this example with modifications to the AssertResultRanked
task:
import com.googlecode.lightest.core.* import com.thoughtworks.selenium.DefaultSelenium /** * Asserts that a given site is ranked on the first results page for a given * search term and search engine. Opens a new Selenium instance for this * purpose, and closes it when done. */ class AssertResultRanked extends LightestTask { /** * Requires: * searchTerm * siteURL * * Optional: * browserStartCommand * searchEngine (defaults to instance of GoogleSearch) */ void doPerform(ITaskResult result) { assert config.'@searchTerm' != null assert config.'@siteURL' != null def searchTerm = config.'@searchTerm' def siteURL = config.'@siteURL' SearchEngine engine = config.'@searchEngine' ?: new GoogleSearch() def selenium = createSelenium(config.'@browserStartCommand', engine.getUrl()) selenium.start() selenium.open('/') selenium.type(engine.getSearchLocator(), searchTerm) selenium.clickAndWait(engine.getSubmitLocator()) def entryLocator = engine.getResultEntryLocator(siteURL) if (! selenium.isElementPresent(entryLocator)) { result.setMessage('The site URL was not found in the results!') result.fail() } selenium.stop() } /** * Returns a new Selenium object that creates tasks as it executes * commands. * * @param browserStartCommand * @param browserURL */ def createSelenium(browserStartCommand, browserURL) { def selenium = new TaskBackedSelenium(browserStartCommand, browserURL) selenium.setContext(getContext()) return selenium } }
Yeah, that's how most people familiar with the Selenium API want to be writing their tests - invoking commands directly off the object! If you run this tests, you will find that each command will be translated into the appropriate task, whose results will be displayed in the report much like they were for the previous testcase iterations! Sweet!
Now I will explain the significance of the behavior added to methodMissing()
for TaskBackedSelenium
.
A quick recap of key points to note about the Selenium java client driver:
doCommand()
method of HttpCommandProcessor
returns String
.DefaultSelenium
API methods delegate to the underlying command processor. Many of these do not care about the value the command processor returns from doCommand()
.doCommand()
, they will call methods like getString()
, which require that doCommand()
return a String
appropriate to the command.Ok, so why even worry about this? Invoking the DoSelenium
task, which is the one that wraps doCommand()
, should always return a result whose message is the String
returned by doCommand()
, right?
Well, not exactly. In most normal cases, the String
will be returned. However, in builder closures, tasks are not performed immediately. "Invoking" the task in a closure does not call its doPerform()
; instead, it causes the builder to create a node representing the task. When the node is "evaluated" at some later point, only then is the task performed. The key point is that, since the task will not be executed immediately in a closure, it will not return a value. For the TaskBackedCommandProcessor
, this will certainly cause an exception to be thrown by methods like getString()
.
Our simple solution is twofold - one, just ignore the exception if we detect we are in a builder closure; and two, don't expect API methods that normally return values to return values when inside builder closures. The code that implements number one is highlighted in TaskBackedSelenium.groovy above. And for number two, we simply avoid looking up Selenium API return values in builder closures. So for example, the following would work:
DoSelenium (command: 'type', target: 'q', value: 'lightest selenium') { selenium.clickAndWait('btnG') }
But we would want to avoid this, because it won't work as expected:
DoSelenium (command: 'type', target: 'q', value: 'lightest selenium') { assertEquals('lightest selenium', selenium.getValue('q')) }
It won't work because at the time assertEquals()
is evaluated, getValue()
is creating a node, not returning a value! However, if you ever wanted to do this:
DoSelenium (command: 'type', target: 'q', value: 'lightest selenium') { selenium.getValue('q') }
no exception would be thrown. This is because the exception thrown under the covers by HttpCommandProcessor
is being hidden from view by the exception handling in methodMissing()
.
Interesting stuff, huh? All this is in an effort to have the framework accommodate a "non-Lightest" way of writing tests. As you can see, it can be done successfully, but requires some careful thought and avoidance of certain pitfalls!
That just about wraps up our tour of Lightest. The Lightest project is really just getting underway, and there are exciting improvements on the horizon. To conclude, I'd like to share what I think some cool features would be:
I'd love to receive feedback on your experiences with Lightest. Drop by the project page at http://code.google.com/p/lightest or file a bug or enhancement request. Also, I hope to get a new website up at http://stressfreetesting.com, where Lightest will be a spotlight technology - there should be more information available there too. Enjoy!
- Haw-Bin Chai (hbchai @t gmail d0t com)


>>
<<
Usr
Pri
JfC
LJ
Phr
Dic
Rel
Voc
!:
Help
User
J50x and J40x Features
Summary of changes in 504 from 503
The main feature of J504 is J64. This true 64 bit J engine runs in Linux64 and Windows XP64 on AMD Athlon or Opteron and Intel EM64T systems. These 64 bit systems run all their 32 bit applications unchanged as well as new 64 bit apps. J64 gives the J programmer full access to the 64 bit address space, including memory mapped files. This is not just another incremental increase in processor speed or data paths. The increase in address space is truly significant for appropriate applications.
See Release Notes for a summary of J Engine changes.
A J installation has 3 parts: (Binary, Library, and Help). A full release has all 3 in a single package. Beta releases may have 3 packages. Noun JVERSION has release info and is displayed in Help|About J. When reporting problems to the J forum it is useful to provide this information in the report.
Plot has been rewritten to provide a clean base for future development. The code is modular, for example, output commands are independent from calculations, so that a plot can now be directed to another format such as EPS. Layout calculations are more accurate, permitting a tighter fit of various plot decorations (labels, tic marks, etc), and ultimately providing a larger window for the actual graphics. For a summary of changes, see Plot Changes.
J now uses the PCRE (Perl Compatible Regular Expression) engine on all platforms. The PCRE library is distributed in the J Binary installation. The PCRE engine is much faster than the old regex library used in previous J Windows releases and provides useful Perl extensions to the core POSIX definitions. See Regular Expression in the User Manual.
Function call depth in previous releases was limited to about 500. The limit is now about 10,000 (PocketPC limit is about 100). This limit is intended to stay within the stack allocated to J by the host. If the stack isn't large enough, you get a crash rather than a limit error. If you suspect problems in this area, do the following simple test:
g=: 3 : 'g c=: >:y.'
g 0
|limit error: g
| g c=:>:y.
c
10004
Big files: The J64 (1!:x) file primitives properly support files >:2^31. The J32 (1!:x) support is limited. In particular, file size (1!:4) and directory (1!:0) report file size as _1 (in Unix the directory file size for files >:2^32 is the 32 bit truncated 64 bit value). In Windows J32, load script bigfiles to work with big files (it could be modified to work in Unix J32).
The (1!:x) file primitives no longer attempt to keep the file pointer unchanged over operations.
Learning J by Roger Stokes and J for C Programmers by Henry Rich have both been updated for this release.
There are 2 new labs: 'Queens and Knights' and 'Special Searches'.
Unix jconsole uses editline for line recall (rather than readline).
Emacs aficiondos should see Emacs in the User Manual for information on how to integrate Emacs into J development with both Jconsole and Jwdp.
LF is the default line end for J engine output in both Windows and Unix. Previously the Windows default was CRLF. Windows profile and configure force LF. You can override this with 9!:37.
New version of solitaire with bug and cosmetic fixes.
Bug fixes discussed in the J Forum are included in this release.
Summary of changes in 503 from 502
Dyadic ;: implements a sequential machine (finite state machine). See labs Sequential Machine and Huffman Coding.
See Release Notes for a summary of J Engine changes. In particular note the performance improvement in append ( , ) and the change in ? and ?. to use Knuth's GB_Flip.
OpenGL support in Jwdp (Java based front end for Unix and Windows). Try the demos and labs. Zach Reiter did this work.
There are several new labs:
Huffman Coding by Roger Hui
Sequential Machine by Roger Hui
Symmetric Array by Roger Hui
The Tower of Hanoi by Roger Hui
Client/Server (Simple Socket) by Eric Iverson
Finite Groups by Ken Iverson
Windows Notification Area by Kirk Iverson
Turtle Geometry - J Turtle User Guide by L. Fraser Jackson
New classes for building standard Client/Server applications are included in '~system\classes\cs'.
The client and server classes run in all J Front Ends (Jconsole, Jwdw, and Jwdp) and all platforms (Windows, Unix, PocketPC). It is now as easy as it can be for you to create a server application and clients that makes use of those services. These classes are introduced in the lab Client/Server (Simple Socket).
An updated version of Henry Rich's J for C Programmers is included.
An updated version of Roger Stokes Learning J is included.
Solitaire (Studio|Demos) was contributed by high school student Ken Cramer. In some Unix systems the simple method of drawing the dragged stack of cards is too slow so the dragging is indicated by a rubber band. Use File|Options to toggle between full drawing and rubber band.
Jwdp now supports wd commands pcolor and setcolor and gl2 commands glmark and glmarkc.
Jwdp also supports wd commands javalaf and javasetlaf for different GUI LAFs (Look and Feel). The Java LAFs are handled by Edit|Configure|Look and Feel. Metal is the default laf and is the recommended portable LAF. The Motif LAF doesn't look good and the Windows LAF is only available in Windows.
See gl2 commands for documentation on new commands glclip, glclipreset, and changes to glcapture.
See wd commands for documentation on changes to clippaste for support of clipboard filenames.
A new wd event handler wdmsg_handler is run when the J application window (result of wd'qhwndx') receives a WM_APP (32768) message. The wdmsg_handler arguments are the integers msgid, wparam, lparam. Lab Windows Notification Area shows how this event handler can be used to make use of the notification icon area on the taskbar.
A verb that executes no non T block sentences previously reported valence error and now returns i.0 0.
Bug fixes discussed in the J Forum are included in this release.
Incompatible changes introduced in 501
The 501 release is a major release (4 to 5). In minor releases (4.05 to 4.06) we do our best to minimize changes which will break existing applications. In a major release we are more cavalier and introduce incompatible changes if we feel there is sufficient long term benefit.
We have concluded that the tridents (except for fork) and some of the bidents complicate the system out of proportion to their utility and they are decommited. See "J 5.01 Non-compatible Changes" in Release Notes for details.
There are also incompatible changes in foreigns, wd, and gl2.
J 4.06 Release
Data displayed with boxes is an important J feature. Previous versions of J for Windows, by default, used oem fonts with linedraw characters for boxes with solid lines. The alternative is +-| ascii characters. Linedraw boxes are pretty, but they are also a never ending nuisance. Oem fonts aren't ansi or unicode and are discouraged by Microsoft and other vendors. They aren't standard in Unix and J for Unix uses +-|. They are a problem in email. In theory html can handle oem fonts, but across platforms and browsers the reality is a nightmare. Jsoftware has decided that easy html documentation, email communication, and standard usage across platforms outweighs pretty boxes. If you want them for your own use, you can still have them.
Jsoftware favors ascii boxes and the default in this release is ansi "Courier New".
The online J books User, Primer, Phrases, Dictionary, and Release Notes are in html. Select menu Help|Help to get an overview of the J Help System and the integrated index.
A preliminary version of 'Learning J' by Roger Stokes and Ken Iverson's 'Computers and Mathematical Notation' are also available through the J Help System.
A new lab 'A J Introduction' by Ken Iverson is in the Languages category. There are five other new labs by Ken in the Live Texts category.
For details on new features in this release see the Release Notes in the J Help System. The following are a few topics covered in those notes.
By default, explicit definitions keep a copy of the original text with whitespace and comments. This is convenient for casual use and development, but can be a significant and unnecessary space overhead in large production systems. Foreign 9!:41 can change the default so that this space is not used. Production systems should startup with 9!:41[0 .
Locales are more efficient and limits have been removed.
Foreigns 9!:36 and 9!:37 provide output formatting control. Adjust output formatting with Edit|Configure|Parameters.
Dll callbacks are supported. See lab: DLL: Callback.
Symbol and unicode, significant new data types, have been added.
try. control structure has been extended and a new control word throw. has been added.
assert. is a new control word.
J 4.05 Release
The prokey license was introduced in J 4.05
business
We have consolidated under the name Jsoftware. Iverson Software Inc. has changed its name to Jsoftware Inc. and does the marketing and sales activities previously run by Strand Software (the Strand staff now work directly with Jsoftware).
documentation
The Help for foreigns, wd commands, and runtime have not been updated. Use them in conjunction with the latest information here.
html help
Help menu item HTML Help runs your browser on documents for new J features. Of particular interest are: Performance Monitor, Mapped Boxed Arrays, Special Code, and Execution Time Limit.
labs
Ken Iverson's Lipshutz lab is a companion to the Seymore Lipshutz "Linear Algebra" of Schaum's Outline Series. Chris Burke's Performance Monitor Utilities lab is an overview of the new performance monitor.
undo/cut/copy/paste (ctrl+zxcv)
The old Win31 shortcuts (alt+backspace/shift+delete/ctrl+insert/shift+insert) are no longer supported in the session manager. This was necessary to simplify resolving conflicts between shortcuts for the session manager, forms, controls, and OCX.
debug
dbr 2 ( 13!:0[2 ) does not require a prokey and records information before clearing the stack. The debug latent expression ( 13!:15 ) executes after the stack is cleared and debug is reset. See HTML Help for more information. The stack information is recorded as:
STACK_ERROR_INFO_base_=: (13!:11;13!:12;13!:13;18!:5) ''jfiles
A change in 3!:1 means that jfiles written with J version 405 cannot be read by earlier versions. J405 can read jfiles created by earlier versions. The change in 3!:1 supports writing and reading binaries in standard and reverse byte order. For more information, see Help|HTML Help|3!:1, 3!:2, and 3!:3 Extended.
regex and socket
The regex (16!:x) and socket foreigns (17!:x) have been decommitted. regex.ijs and socket.ijs scripts now use dll call (cd) to provide the same services. This is similar to how we previously decommitted the data driver (14!:x) foreigns. This results in a smaller, more portable J engine and in more open systems where you have full access to the scripts and underlying system services.
Socket verbs return the result code linked with the result, rather than catenated as in previous releases.
command line
The form is: [filename] [ /command [parameter] ] ..
Standard profile (system\extras\config\profile.ijs) is run unless there is a /jnoprofile command.
Filename is run after the standard profile.
There are 3 types of commands: windows, J startup, and application.
windows:
/register (or /regserver) - register JEXEServer and JDLLServer
/unregister (or /unregsever) - unregister JEXEServer and JDLLServer
/embedding - start as Automation server for COM client
J startup (start with j, as will new ones)
/jnoprofile - start without standard profile
/jtemp path - temp directory
/jddename servername
/jrt - runtime application (see runtime section)
Application commands can be used in the application (wd'qmdline').
Examples:
profile (show session)
c:\j.exe
profile (show session), run foo.ijs
c:\j.exe user\foo.ijs
profile (hide session), run foo.ijs (simple runtime app)
c:\j.exe user\foo.ijs /jrt
no profile, run foo.ijs (production runtime app)
c:\j.exe app.ijs /noprofile /jrt
window driver
wd'...' 65k argument limit removed and 65k result limit is now 500k.
makejr - decommitted (see runtime section)
makejl - decommitted (see runtime section)
picon filename n - set form icon with icon n from file (exe, dll, or ico file) wd'picon system\examples\data\jy.ico 0' qrt - decommitted (see runtime section) smicon filename n - set sm icon (see picon)
runtime
The J license is free and you can distribute runtime applications that include as much of the J system as required.
Simple runtime applications can use the standard profile. Production runtime systems should be built with the Project Manager.
/rt parameter is decommited. You have to rework old runtime applications.
/jrt parameter causes the standard profile to not do an smmfshow command to show the session manager. The runtime app can have the standard profile, but not show the session manager. If there is an error and the session manager is invisible, J terminates with a message box saying there was an error. If J finishes execution and the session manager is invisible and there are no forms, then J terminates.
wd'picon ...' sets form icons for the application.
wd commands makeijr and makeijl are desupported. ijr files are no longer supported. ijl files are created by 3!:6 .
system\examples\demo\runtime.ijs is a simple runtime application. Open and experiment with the script. Run it from an icon or Start|Run with command line:
c:\j405\j.exe system\examples\demo\runtime.ijs /jrtsystem\examples\runtime\bldrt.bat creates a production runtime application setup and distribution. Study the bldrt.bat file and run it to create a distributable J runtime application.
ocx controls
OCX controls with ids with blanks are supported by the form editor with quotes. For example: "ocx:rmocx.RealPlayer G2 Control.1"
OLE command picture and object arguments start with a !. For example:
wd'olemethod images listimages add ,, !picture:abc.bmp'If the argument contains blanks, it must be quoted, but then it can't be distinguished from quoted data. A ! by itself is an escape so that the next parameter can be a quoted picture or object argument. For example:
wd'olemethod ... ,, ! "!picture:my abc file.bmp"
addons (LAPACK and FFTW)
J addons are installed in the J addon directory. Old addon downloads won't install in the proper place for J4.05 and new downloads won't install in the proper place for previous releases.
COM
The j.exe and j.dll tlb files are now included as resources and are no longer separate files. The J installation registers both the JEXEServer and JDLLServer COM objects. Use jreg.bat to register or unregister.
J 4.04 Release
Release 4.04 has sparse arrays, changes in J COM objects, and OCX license support.
Sparse arrays
Sparse arrays provide a compact and efficient storage form for very large arrays where most elements are zero or some other "sparse element". The sparse array representation does not store extra copies of the sparse element. J primitives work directly on sparse arrays. A new verb $. converts between sparse and dense representations of arrays.
In this release, only numeric arrays can be sparse. Subsequent releases will support sparse character and boxed arrays.
Run lab Sparse Arrays for a quick overview of the new facility. For detailed information, see Help|HTML Help|Sparse Arrays.
J COM objects
GetB and ErrorTextB methods in previous versions incorrectly returned a BSTR with a count that included the terminating NULL. This bug has been fixed. Applications with workarounds may need fixing.
Multiple JDLLServer COM objects are now supported. Each JDLLServer object created is a complete new instance of J. Previously each JDLLServer object shared the same globals (for example, symbol tables) and was not useful. It is now possible to efficiently create completely independent, in-process, J COM objects.These independent J COM objects can run in the same threads or in different threads.
The direct DLL interface to J requires new, explicit calls to create and free a J instance and the instance handle is a new parameter to all other calls. For example:
HANDLE pj = JInit(); // get handle for new J instance
JDo(pj, "a=:i.5");
JFree(pj); // free J instance
OCX license
OCX controls distributed with a J application can be distributed in a runtime version that requires license information when they are created. On a system with the design time OCX installed, use olegetlic to get the license The progid is the same id used in the cc command (without the ocx: prefix). To create the OCX on a system with a runtime OCX, use olesetlic to set the license. The following example uses the FarPoint spreadsheet control.
fpkey =: wd'olegetlic FPSpread.Spread.2'
fpkey
67 0 111 0 112 0 121 0 114 0 105 0 103 0 104 0 116 0 32 0 ...
(".fpkey) { a. NB. 2 byte unicode is often readable
Copyright ...
NB. in a distributed application set key before cc
wd'olesetlic FPSpread.Spread.2 ',fpkey
wd'cc ss ocx: FPSpread.Spread.2'
load utility
The load utility has changed, when loading scripts from a directory that includes a project file. If the left argument of load is not given, then a default locale is used. Previously, this locale was always base; now, if the directory contains a project file, the default locale used is that specified in the projects target locale. This makes it easy to work with individual scripts in a project, since they will automatically load into the projects target locale.
Debug
The Debug window now includes single-step and single-step-into buttons, plus several other commands available on shortcut keys.
The toolbar Open button now responds to the current cursor position. If the name at the cursor is a noun, its definition is displayed in a viewer; otherwise the script where the name is defined is opened.
See Debug for more details, or with the Debug window active, press Ctrl-H for a list of the available shortcut keys.
J 4.03 Release
Code Editor
The new code editor was written by Andrei Stcherbatchenko and we're sure you'll find it makes life as a J programmer more exciting and productive.
customized code coloring (Edit|Configure)
large files (up to 10,000 lines)
multiple undo and redo
drag and drop editing
standard arrow/home/end shortcuts (ctrl /shift modifiers)
Ins toggles overstrike/insert
left margin
quick line selection with mouse
ctrl+shift+0-9 toggles mark
alt+0-9 scrolls mark visible
ctrl+shift+F2 clears marks
Tab indents (shift+Tab exdents) selected lines
fixed pitch font required
ijx output truncated with . . . at 256 characters
ctrl+shift+up/down arrow recalls lines (used to be ctrl+up/down)
Form colors
You can set form and control (text, text background, and background) colors.
wd'pcolor R G B' NB. form color wd'setcolor id textR G B textbkgndR G B bkgndR G B'
You can add these commands in form initialization.
abc_run=: 3 : 0
wd ABC
NB. initialize form here
wd 'pcolor 0 0 255'
wd 'setcolor cceditm 255 0 0 0 0 255 0 0 255'
wd 'pshow;'
)
The setcolor command can override the gray readonly edit boxes. The setcolor command has no effect on push buttons or the dropdown listbox of a combobox (unfortunate Window facts).
Debug
This release includes a preliminary version of a debug GUI. It is built on top of facilities that have been in J for some time. This version is not complete, and requires changes in the J engine before it can provide all the facilities we envision. However, we found that it is already so useful that we decided to include it in this release. It is a taste of things to come.
Start Debug with Run|Debug. Press the Help button to see documentation. Learn about Debug with the Debug lab.
HTML Publish
Use Run|HTML Publish, or Tools|HTML Publish from Project Manager, to convert scripts to HTML format for the web. Hold down the shift key to convert the active window.
Popup Menu
The release includes Oleg Kobchenko's popup menu, as enhanced by Alex Kornilovski. For more information, see scripts system\packages\winapi\menu.ijs and menudemo.ijs.
load 'menu'
wdmenu ' one two three'
ODBC
This release decommits the Data Driver foreign family 14!:x . This interface was written in C and was a closed, black box to J programmers. It didn't support newer versions of the ODBC API and was missing important features.
The new ODBC support was written by John D. Baker and is provided by scripts that use DLL calls to directly access the ODBC API. The J programmer now has the same ODBC access as the C programmer. The new script dd.ijs provides the same functionality as the previous version with DLL calls to the ODBC API, rather than with 14!:x calls. An application that uses dd.ijs should work with the new dd.ijs. There are some performance improvements and significant new functionality such as support for stored procedures. If you are interested in ODBC, work your way through the new ODBC labs.
FFTW AddOn
In addition to the Lapack AddOn, there is now an FFTW AddOn. FFTW is a collection of fast C routines for computing the Discrete Fourier Transform in one or more dimensions. It includes complex, real, and parallel transforms, and can handle arbitrary array sizes efficiently. The FFTW AddOn consists of a DLL incorporating the FFTW routines, plus supporting J scripts and labs. The AddOn is currently only available for Windows 9x/NT. For more information and to download the FFTW AddOn, visit www.jsoftware.com (or use the J cdrom AddOn directory).
Project Manager (PM)
There are several improvements in the Project Manager. If you're an experienced J user and you don't use PM, then we suggest it is time to start. It is great for both small and large projects. If you're new to J, start out right by using PM for all your projects. Start PM from menu command Run|Project Manager.
wd commands
New wd commands (documented in Help|wd commands):
glgridspace x y - space in from upper left for text
pcolor R G B - form background color
setcolor id textR G B textbkgndR B G bkgndR B G - control color
smcolor n R G B - code editor color
smkeywords n keywords - code editor keywords (for color)
smreplace text - replace selected text
smgetscroll - scroll position of top line
smflush [bool] - force immediate (unbuffered) output
Enter in a readonly editm control is an enter event.
Miscellaneous
File menu MRU (most recently used) files has 8 items and is retained between sessions.
Bug fixes (thanks to all you out there, particulary J forum members, for giving us the chance to fix them!).
try. catch. honors debug stops.
Several performance improvements in the J engine.
J 4.02 Release
Try the new demos pousse and eigenpictures (J LAPACK AddOn is required for eigenpictures).
Take a look at the new lab Fractals Visualization, & J.
Run labs Mapped Names and Files and Mapped File Database to learn about these new facilities. A mapped file can be accessed as if it were memory and a mapped name is an array that is a file.
Run labs DLL: Writing and Using a DLL and DLL: Using System DLLs (file examples) to learn about changes to the 15!:x DLL call facilities. The User Manual chapter DLLs and Memory Management has been updated to reflect the changes. Some of the changes are incompatible with previous releases. Previously this facility was only available in Win95 and NT. It will now be available on all platforms.
A J AddOn is a separately packaged installation that is added on to the base J installation.
The complete LAPACK library is now available with the J LAPACK AddOn. LAPACK (Linear Algebra Package) is a set of routines for solving systems of simultaneous linear equations, least-squares solutions of linear systems of equations, eigenvalue problems, and singular value problems. The associated matrix factorizations (LU, Cholesky, QR, SVD, Schur, generalized Schur) are also provided, as are related computations such as reordering of the Schur factorizations and estimating condition numbers.
Download jlapack.exe and run it after you have installed J4.02. This adds the LAPACK files to the J directory structure. When it is installed, run lab LAPACK to learn about this powerful new addition to J.
J 4.02 Interpreter
The dyad {. has been extended to accept infinities in the left argument, with an infinite value specifying the length of the corresponding axis.
a=: i.4 5
a
0 1 2 3 4
5 6 7 8 9
10 11 12 13 14
15 16 17 18 19
2 _ {. a NB. first 2 rows and all columns
0 1 2 3 4
5 6 7 8 9
_ 2 {. a NB. all rows and first two columns
0 1
5 6
10 11
15 16
_1 __ {. a NB. last row and all columns
15 16 17 18 19
_ {. a NB. all rows and all columns
0 1 2 3 4
5 6 7 8 9
10 11 12 13 14
15 16 17 18 19
The monad i: has rank 0; i: n gives integers from -n to n inclusive; i: a j. b gives numbers from -a to a in b equal steps.
i: 5
_5 _4 _3 _2 _1 0 1 2 3 4 5
i: _5
5 4 3 2 1 0 _1 _2 _3 _4 _5
i: 5j4
_5 _2.5 0 2.5 5
i: _5j4
5 2.5 0 _2.5 _5
i: 2.5j4
_2.5 _1.25 0 1.25 2.5
i: 5j_4
|domain error
| i:5j_4
On extended precision arguments, the determinant -/ .* runs faster in less space.
ts=: 6!:2 , 7!:2@] NB. time and space
ts '-/ .* h' [ h=: % >: +/~ i.10x
0.14 37888
ts '-/ .* h' [ h=: % >: +/~ i.20x
2.073 291520
J 4.01 Release
Use =: for global definitions in scripts. Run|Window and Run|File use load and definitions made with =. are local to load and disappear when it finishes.
The J file suffix has changed from .j? to .ij? ( .js to .ijs ) to avoid javascript conflicts. Use system\extras\migrate\ext.ijs if you have lots of files.
system directory contains the other distributed directories (main\stdlib.js is now system\main\stdlib.ijs).
>>
<<
Usr
Pri
JfC
LJ
Phr
Dic
Rel
Voc
!:
Help
User


Court orders PBI to further investigate Shomi Kaiser case
by bdnews24.comA Dhaka court has ordered the Police Bureau of Investigation to further investigate a defamation lawsuit filed against actress Shomi Kaiser for her ‘insulting’ behaviour toward journalists.
Dhaka Metropolitan Magistrate Ziaur Rahman passed the order on Monday after hearing an appeal filed by the plaintiff against an investigation report submitted by the police.
Mahbubur Rahman, investigation officer posted to Shahbagh Police Station, submitted his final report to the court on Oct 24. The report said allegations against the actress were found to be ‘false.’
Actress Shomi Kaiser was a guest in the inaugural ceremony of an e-commerce-based tourism site ‘Bindu 365’ on Apr 24 at the National Press Club in Dhaka. She could not find her two smartphones after delivering her speech.
Afterwards, about 50 journalists were held inside the auditorium with the main exit closed and her bodyguards searching the bags of several reporters for about 10 minutes.
The situation heated up after some journalists protested the incident and tried to leave the venue.
The actress then labelled the journalists as ‘thieves,’ alleged Nuzhatul Hasan, editor of Student Journal BD.
Later, Nuzhatul Hasan started a case with Ramna Police Station on Apr 30. The court granted the appeal of the journalist and asked the chief of the police station to submit a report related to the allegation by Jun 16.


Add Sub Category
×
Select image
Change
Remove
face
Be The Drop
Home
Pending Donations
Accepted Donations
Received Donation history
View Products
Customer Orders
3
face
Add Product
Block Account
Customer
Donor
Be The Drop
Service to humanity is service to God
Customers Orders
Order Date
Product Name
Price
Quantity
Address
Order Date
Product Name
Price
Quantity
Address


Home
Products
Cart
Your Profile
Admin Site
Logout
Edit Password
Old Password
New Password
MyCart
Sales
EditPassword
athena-kay
Email:
<EMAIL_ADDRESS>Phone:
+5567 768 565
User Type: Attendant
Total Amount Sold: KSH 3,457,576


{% load i18n %} {% spaceless %}
{% trans "The SLM practice" context "The SLM practice [questionnaire] compiled by [user] has been deleted from the Global SLM Database." %} {{ questionnaire_name }} {% if compiler %} {% trans "compiled by" context "The SLM practice [questionnaire] compiled by [user] has been deleted from the Global SLM Database." %} {{ compiler }} {% endif %} {% trans "has been deleted from the Global SLM Database." context "The SLM practice [questionnaire] compiled by [user] has been deleted from the Global SLM Database." %}
{% endspaceless %}


<= Go Back To Calculator
Enjoy Snake Game !!!
Only Keyboard based PC browser Supported
Mobile Touch supported will be added in future


MISTER ROGER'S NUMBERHOOD
WHAT"S YOUR NAME?
ENTER A NUMBER TO HEAR THE SOUNDS OF THE NUMBERHOOD!
Submit!
Reverse
Remove


A traditional fruit cake covered in icing and decorated with a snowman, holly and Christmas puddings.
A traditional fruit cake covered in icing and decorated with Christmas Presents.
Small balls of Christmas cake dusted with icing sugar and holly made from icing.
A sponge cake with a decorative plaque with an iced message.
An iced cake with matching cupcakes and mini cupcakes.
An iced sponge cake with iced roses and faux wedding rings.
Cupcakes with iced decorations with frosting.
Cupcakes with decorations made from icing.
Cupcakes made from traditional fruitcake with frosting and Christmas tree decorations.


Privacy Statement
Safeguarding Your Privacy
At Troopmaster Software, we take your privacy very seriously. Integrity, honesty, and trust are important Scouting values and we value them as well.
Types of Information We Collect
In our normal course of business, we collect the basic information we need to process your order and to support your unit. This includes name, address, phone number,and email address. If you purchase by credit card, we also collect your card number and expiration date.
Safeguarding Your Information
Troopmaster Software maintains physical, electronic, and procedural security over your customer information. Access to this information is strictly limited to employees who need this information to perform their job functions.
How We Use Your Information
Your personal data and credit card information is used only by our employees in our normal course of business. Troopmaster Software never shares, sells, or releases your personal information to anyone without your express permission. The lone exception is that your credit card information is provided to the credit card processing company, which of course is necessary to process your order.
General Privacy Policy Provisions
This privacy policy has been in effect since our company was first founded (1985). Any future policy changes will be posted on this web site


This site lets you provide labels for wood knot images that will be used to train machine learning classifiers. Next, go to the ‘Learn to Label’ wizard to learn to recognize the different categories of images (we'll quiz you to see how accurate you are). Once you are trained, you can continue to the ‘Label Images’ wizard to contribute labels to our database.
The Principal Classification
The categorization of wood knots is related to how the knot integrates into the surrounding wood.
Sound knot
A knot grown firmly into the surrounding wood material and does not contain any bark or signs of decay. The color may be very close to the color of sound wood.
Dry knot
A firm or partially firm knot, and has not taken part to the vital processes of growing wood, and does not contain any bark or signs of decay. The color is usually darker than the color of sound wood, and a thin dark ring or a partial ring surrounds the knot.
Encased knot
A knot surrounded totally or partially by a bark ring. Compared to dry knot, the ring around the knot is thicker.
Here you will see wood knot images from the labeled data set. Try label them and once you have gotten enough correct in a row, you can move on to labelling images.


{{lessons.lessonName}}
Lesson Taker: {{lessons.lessonTaker}}
Current Lessons
|
|
Third Lesson | |
|
|
Design Psychology | |
|
|
The Basics Of Typography |
Enrolled Students
| Name | Course Name | Language | Fee |
|---|---|---|---|
| Person 1 | Design Psychology | English | $700/hr |
| Person 2 | The Basics Of Typography | English | $700/hr |
| Person 3 | Math | English | $700/hr |


° 기본 설정
foreach ($basic as $o): ?>
- =$o->comment?>=$o->title?>
- =$o->value?>
endforeach; ?>
° 회원가입 설정
foreach ($register as $o): ?>
- =$o->comment?>=$o->title?>
- =$o->value?>
endforeach; ?>
° 게시판 설정
foreach ($board as $o): ?>
- =$o->comment?>=$o->title?>
- =$o->value?>
endforeach; ?>
° 아이콘 설정
foreach ($icon as $o): ?>
- =$o->comment?>=$o->title?>
- =$o->value?>
endforeach; ?>


Commands
Edward provides a series of commands to manage your local services, which are called using the form:
$ edward [command]
You may have discovered some of these already in the Quickstart, but they are outlined in more detail here.
List
The list
command outputs a list of all the services and groups that are defined
in the current edward.json
. It takes no arguments.
Groups are listed first, followed by services. The list will include all services
that are defined by your edward.json
or any of the files it imports.
Start
The start
command will build and launch a service or group locally. It takes
the name of one or more services as arguments.
For example, to start the service named myservice, you would run:
$ edward start myservice
To start the group mygroup along with myservice, you would run:
$ edward start mygroup myservice
When starting multiple services, it will build and launch them sequentially.
If any of the specified services are already running, Edward will ignore them and move on to the next service.
If Edward fails to build or launch any services, it will stop the operation and any subsequent specified services will not start.
Once all services have been started, the Edward command will exit, and these services will continue to run in the background.
If you want to go straight into the logs for your services once they are started, provide the --tail
flag:
$ edward start --tail mygroup
If any of the services being started are configured with watch paths,
they will be automatically restarted when any files in those paths are edited. You can disable this by using the
--no-watch
flag when starting these services:
$ edward start --no-watch mygroup
Stop
The stop
command will stop one or more groups and/or services. It takes service
and group names as arguments in the same manner as start
.
For example, to stop mygroup and myservice:
$ edward stop mygroup myservice
If any of the specified services are not running, Edward will ignore them and move on to the next service. If Edward fails to stop a service, it will continue to stop the rest of the services specified.
The stop
command by default will only stop services that are managed by the current config file. If you want to stop other services that are managed by Edward, you can use the -a
flag.
Restart
The restart
command will rebuild and relaunch the specified groups/services. It takes service
and group names are arguments in the same manner as start
.
To restart mygroup:
$ edward restart mygroup
Each service will be stopped, rebuilt and relaunched sequentially. If Edward fails to start, build or launch any
service, the operation will end, as with start
.
The restart
commands also supports the --tail
and --no-watch
flags as with start
.
Status
The status
command will display the status of one or more groups and/or services. It takes service
and group names are arguments in the same manner as start
.
For example, to get the status of myservice:
$ edward status myservice
To see the status for all services:
$ edward status
This will only show running services that are managed by the current config file. If you want to see all service that are managed by Edward, including those from other config files, you can use the -a
flag.
Log/Tail
The log
or tail
command will output and then follow the console logs for the specified groups/services.
For example, to output and follow the logs for myservice:
$ edward log myservice
Or:
$ edward tail myservice
If more than one service is being output, the name of the service will be added to the start of each line in the log to distinguish them.
Generate
The generate
command will search in the current working directory for projects for which Edward
can automatically generate a config file. It will then either create a new edward.json
file in the
working directory, or add to the existing file.
For example:
$ edward generate
Will search in all directories below the working directory and automatically generate config for any supported projects that are found.
If you want to only generate config for a specific project, you can specify it as an argument:
$ edward generate myservice
Edward supports autogeneration for four types of project:
- go
- Docker
- icbm
- Procfile
You can use the --target
flag to limit the types of projects for which services are generated. For example:
$ edward generate –target go –target docker
Will generate services from only go and Docker projects.

This generator will match any folder containing a main
package. The name of this
folder will be used as the name of the service.
The generated config will build the project by changing into the package directory and running go install
with no additional arguments. The name of the project will be used to launch the resulting binary.
The generated config will assume that a service has started successfully by detecting that it is listening on at least one port. If a service does not listen on any ports, it will time out when starting.
The Go generator will attempt to configure the watch paths for Go services, based on the imports of the project’s source.
Note that this generator assumes that your GOPATH is configured correctly to build discovered projects.
Once a Go service has been found, any folders inside the package directory will not be searched.
Docker
The Docker generator will create service configuration for Docker containers.
This generator will match any folder containing a Dockerfile. The name of this folder will be used as the name of the service.
The generated config will build the container using docker build
and start it using docker run
. An
Edward specific tag will be used to identify these container instances.
The port to be used for this service will be identified by the EXPOSE
command in the Dockerfile. This same port will be opened locally for this service. Edward will identify that this container has started successfully when the
exposed port is open. If EXPOSE
is not used, starting the container will time out.
Note that this generator will assume that you can execute the docker
command without additional configuration, so older Docker Toolkit distributions may not work.
icbm
The icbm generator will generate service configuration for services that use the icbm build tool.
This generator will look for a build.spec file and generate a service for each of the named aliases.
The generated config will assume that a service has started successfully by detecting that it is listening on at least one port. If a service does not listen on any ports, it will time out when starting.
Procfile
The Procfile generator will generate service configuration for projects that contain a Procfile, such as used on Heroku.
This generator will look for a file called Procfile and create a service for each process listed inside. These services will be added to a group representing the whole project.
The generated config will assume that a service has started successfully by detecting that it is listening on at least one port. If a service does not listen on any ports, it will time out when starting.
Ignoring directories
To protect against false positives, you can instruct Edward to ignore specific patterns when running generate
by creating an .edwardignore file.
This file uses the same format as gitignore. You can place an .edwardignore file in any directory and it will take effect for paths below that directory, replacing ignores specified by ignore files higher up.


You need to run this from a file server. From the root of this project run $ serve then open up http://localhost:5000/example.html.


Monday Memo: Big airshow and earnings reports
by Seattle Times staffTHIS WEEK:The Dubai Airshow runs Sunday through Thursday. With the 737 MAX grounded and the widebody jet market slow, Boeing expects little in the way of big orders, while Airbus will likely announce some sales coups. Boeing observers will look for clarity on plans from Emirates, the giant Gulf carrier whose massive 2014 order for 150 of Boeing’s new 777X is still on the books. Emirates CEO Tim Clark has considered whether to swap out some for an order of 787 Dreamliners.
TUESDAY: Home Depot reports earnings.
WEDNESDAY: Target releases quarterly results.
THURSDAY: Macy’s reports earnings.
Seattle Times staff