Merge branch 'stable-2.11'

* stable-2.11:
  Add CommitValidationListener example
  Consume API version 2.11.4

Change-Id: I37fa123d5cf9121050537d7fb31801e8c7767b5c
diff --git a/BUCK b/BUCK
index b28f661..f47a479 100644
--- a/BUCK
+++ b/BUCK
@@ -19,10 +19,11 @@
 
 java_test(
   name = 'cookbook_tests',
-  srcs = glob(['src/test/java/**/*.java']),
-  deps = [
+  srcs = glob(['src/test/java/**/*IT.java']),
+  labels = ['cookbook-plugin'],
+  source_under_test = [':cookbook-plugin__plugin'],
+  deps = GERRIT_PLUGIN_API + GERRIT_TESTS + [
     ':cookbook-plugin__plugin',
-    '//lib:junit',
   ],
 )
 
diff --git a/lib/BUCK b/lib/BUCK
new file mode 100644
index 0000000..9c596fa
--- /dev/null
+++ b/lib/BUCK
@@ -0,0 +1,26 @@
+include_defs('//bucklets/maven_jar.bucklet')
+
+maven_jar(
+  name = 'guava',
+  id = 'com.google.guava:guava:19.0-rc2',
+  sha1 = '93e17f60bc524c2610b41c494bb829c11ca89436',
+  license = 'Apache2.0',
+)
+
+maven_jar(
+  name = 'junit',
+  id = 'junit:junit:4.11',
+  sha1 = '4e031bb61df09069aeb2bffb4019e7a5034a4ee0',
+  license = 'DO_NOT_DISTRIBUTE',
+)
+
+maven_jar(
+  name = 'truth',
+  id = 'com.google.truth:truth:0.27',
+  sha1 = 'bd17774d2dc0fffa884d42c07d2537e86c67acd6',
+  license = 'DO_NOT_DISTRIBUTE',
+  exported_deps = [
+    ':guava',
+    ':junit',
+  ],
+)
diff --git a/lib/gerrit/BUCK b/lib/gerrit/BUCK
index 8d4790a..968cdb1 100644
--- a/lib/gerrit/BUCK
+++ b/lib/gerrit/BUCK
@@ -1,12 +1,19 @@
 include_defs('//bucklets/maven_jar.bucklet')
 
-VER = '2.11.4'
-REPO = MAVEN_CENTRAL
+VER = '2.12-SNAPSHOT'
+REPO = MAVEN_LOCAL
+
+maven_jar(
+  name = 'acceptance-framework',
+  id = 'com.google.gerrit:gerrit-acceptance-framework:' + VER,
+  license = 'Apache2.0',
+  attach_source = False,
+  repository = REPO,
+)
 
 maven_jar(
   name = 'plugin-api',
   id = 'com.google.gerrit:gerrit-plugin-api:' + VER,
-  sha1 = 'c112d2c35250ea3d75c9388a4167cd6b45c7a37c',
   license = 'Apache2.0',
   attach_source = False,
   repository = REPO,
@@ -15,7 +22,6 @@
 maven_jar(
   name = 'gwtui-api',
   id = 'com.google.gerrit:gerrit-plugin-gwtui:' + VER,
-  sha1 = '67617dd41acd90c2dfa28368871b23ecefca3d08',
   license = 'Apache2.0',
   attach_source = False,
   repository = REPO,
diff --git a/pom.xml b/pom.xml
index 0bcd9cf..b7ff1cc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
   <groupId>com.googlesource.gerrit.plugins</groupId>
   <artifactId>cookbook-plugin</artifactId>
   <packaging>jar</packaging>
-  <version>2.11.4</version>
+  <version>2.12-SNAPSHOT</version>
   <properties>
     <Gerrit-ApiType>plugin</Gerrit-ApiType>
     <Gerrit-ApiVersion>${project.version}</Gerrit-ApiVersion>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/Greetings.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/Greetings.java
index 58a9017..f53b066 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/cookbook/Greetings.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/Greetings.java
@@ -17,17 +17,12 @@
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.server.change.RevisionResource;
-import com.google.inject.Inject;
 
 import java.util.ArrayList;
 import java.util.Collection;
 
 class Greetings implements RestReadView<RevisionResource> {
 
-  @Inject
-  Greetings() {
-  }
-
   @Override
   public Response<Collection<GreetInfo>> apply(RevisionResource rev) {
     Collection<GreetInfo> l = new ArrayList<>(3);
@@ -40,7 +35,7 @@
     return Response.ok(l);
   }
 
-  class GreetInfo {
+  static class GreetInfo {
     String message;
     String country;
     String href;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/HelloWeblink.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/HelloWeblink.java
index 248dad3..48829c5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/cookbook/HelloWeblink.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/HelloWeblink.java
@@ -16,10 +16,12 @@
 
 import com.google.gerrit.extensions.common.WebLinkInfo;
 import com.google.gerrit.extensions.webui.BranchWebLink;
+import com.google.gerrit.extensions.webui.FileHistoryWebLink;
 import com.google.gerrit.extensions.webui.PatchSetWebLink;
 import com.google.gerrit.extensions.webui.ProjectWebLink;
 
-public class HelloWeblink implements PatchSetWebLink, ProjectWebLink, BranchWebLink {
+public class HelloWeblink implements PatchSetWebLink, ProjectWebLink,
+    BranchWebLink, FileHistoryWebLink {
   private String name = "HelloLink";
   private String placeHolderUrlProject =
       "http://my.hellolink.com/project=%s";
@@ -27,6 +29,8 @@
       "http://my.hellolink.com/project=%s-branch=%s";
   private String placeHolderUrlProjectCommit =
       placeHolderUrlProject + "/commit=%s";
+  private String placeHolderUrlProjectRevisionFileName =
+      placeHolderUrlProject + "-revision=%s-file=%s";
   private String myImageUrl = "http://placehold.it/16x16.gif";
 
   @Override
@@ -52,4 +56,14 @@
         String.format(placeHolderUrlProjectCommit, projectName, commit),
         Target.BLANK);
   }
+
+  @Override
+  public WebLinkInfo getFileHistoryWebLink(String projectName, String revision,
+      String fileName) {
+    return new WebLinkInfo(name,
+        myImageUrl,
+        String.format(placeHolderUrlProjectRevisionFileName, projectName,
+            revision, fileName),
+        Target.BLANK);
+  }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/HelloWorldServlet.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/HelloWorldServlet.java
index d2c3935..b1493c2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/cookbook/HelloWorldServlet.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/HelloWorldServlet.java
@@ -46,12 +46,12 @@
       final HttpServletResponse rsp) throws IOException, ServletException {
     rsp.setContentType("text/html");
     rsp.setCharacterEncoding("UTF-8");
-    final Writer out = rsp.getWriter();
-    out.write("<html>");
-    out.write("<body>");
-    out.write("<h2>Hello world!</h2>");
-    out.write("</body>");
-    out.write("</html>");
-    out.close();
+    try (Writer out = rsp.getWriter()) {
+      out.write("<html>");
+      out.write("<body>");
+      out.write("<h2>Hello world!</h2>");
+      out.write("</body>");
+      out.write("</html>");
+    }
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/Module.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/Module.java
index 814c295..b182159 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/cookbook/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/Module.java
@@ -25,6 +25,7 @@
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.restapi.RestApiModule;
 import com.google.gerrit.extensions.webui.BranchWebLink;
+import com.google.gerrit.extensions.webui.FileHistoryWebLink;
 import com.google.gerrit.extensions.webui.PatchSetWebLink;
 import com.google.gerrit.extensions.webui.ProjectWebLink;
 import com.google.gerrit.extensions.webui.TopMenu;
@@ -47,6 +48,7 @@
     DynamicSet.bind(binder(), PatchSetWebLink.class).to(HelloWeblink.class);
     DynamicSet.bind(binder(), ProjectWebLink.class).to(HelloWeblink.class);
     DynamicSet.bind(binder(), BranchWebLink.class).to(HelloWeblink.class);
+    DynamicSet.bind(binder(), FileHistoryWebLink.class).to(HelloWeblink.class);
     DynamicSet.bind(binder(), ServerPluginProvider.class).to(
         HelloSshPluginProvider.class);
     DynamicSet.bind(binder(), UsageDataPublishedListener.class).to(UsageDataLogger.class);
@@ -65,6 +67,8 @@
         .to(MergeUserValidator.class);
     DynamicSet.bind(binder(), HashtagValidationListener.class)
         .to(HashtagValidator.class);
+    DynamicSet.bind(binder(), CommitValidationListener.class)
+        .to(CommitValidator.class);
     DynamicSet.bind(binder(), NewProjectCreatedListener.class)
         .to(ProjectCreatedListener.class);
     DynamicSet.bind(binder(), CommitValidationListener.class)
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/BuildsDropDownPanel.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/BuildsDropDownPanel.java
new file mode 100644
index 0000000..86b7034
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/BuildsDropDownPanel.java
@@ -0,0 +1,74 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.cookbook.client;
+
+import com.google.gerrit.plugin.client.extension.Panel;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.InlineHyperlink;
+import com.google.gwt.user.client.ui.InlineLabel;
+
+/**
+ * Extension for change screen that displays a status in the header bar.
+ */
+public class BuildsDropDownPanel extends FlowPanel {
+  static class Factory implements Panel.EntryPoint {
+    @Override
+    public void onLoad(Panel panel) {
+      panel.setWidget(new BuildsDropDownPanel());
+    }
+  }
+
+  BuildsDropDownPanel() {
+    Grid g = new Grid(3, 4);
+    g.addStyleName("infoBlock");
+    CellFormatter fmt = g.getCellFormatter();
+
+    g.setText(0, 0, "State");
+    fmt.addStyleName(0, 0, "header");
+    g.setText(0, 1, "PS");
+    fmt.addStyleName(0, 1, "header");
+    g.setText(0, 2, "Date");
+    fmt.addStyleName(0, 2, "header");
+    g.setText(0, 3, "Log");
+    fmt.addStyleName(0, 3, "header");
+
+    HorizontalPanel p = new HorizontalPanel();
+    p.add(new Image(CookBookPlugin.RESOURCES.greenCheck()));
+    p.add(new InlineLabel("OK"));
+    g.setWidget(1, 0, p);
+    g.setWidget(1, 1, new InlineLabel("2"));
+    g.setWidget(1, 2, new InlineLabel("2015-07-09 11:06:13"));
+    g.setWidget(1, 3, new InlineHyperlink("Build Log", "TODO"));
+
+    p = new HorizontalPanel();
+    p.add(new Image(CookBookPlugin.RESOURCES.redNot()));
+    p.add(new InlineLabel("FAILED"));
+    g.setWidget(2, 0, p);
+    g.setWidget(2, 1, new InlineLabel("1"));
+    g.setWidget(2, 2, new InlineLabel("2015-07-09 09:17:28"));
+    g.setWidget(2, 3, new InlineHyperlink("Build Log", "TODO"));
+
+    fmt.addStyleName(0, 0, "topmost");
+    fmt.addStyleName(0, 1, "topmost");
+    fmt.addStyleName(0, 2, "topmost");
+    fmt.addStyleName(0, 3, "topmost");
+
+    add(new PopDownButton("Builds", g));
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/ChangeScreenPreferencePanel.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/ChangeScreenPreferencePanel.java
new file mode 100644
index 0000000..b610f87
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/ChangeScreenPreferencePanel.java
@@ -0,0 +1,154 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.cookbook.client;
+
+import com.google.gerrit.client.info.AccountPreferencesInfo;
+import com.google.gerrit.plugin.client.Plugin;
+import com.google.gerrit.plugin.client.extension.Panel;
+import com.google.gerrit.plugin.client.rpc.RestApi;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.VerticalPanel;
+
+import java.util.Map;
+
+public class ChangeScreenPreferencePanel extends VerticalPanel {
+  private static final String DEFAULT = "DEFAULT";
+  private static final String COOKBOOK = "COOKBOOK";
+  private static final String OTHER = "OTHER";
+  private static final String DEFAULT_URL_MATCH = "/c/(.*)";
+  private static final String COOKBOOK_URL_TOKEN =
+      "/x/" + Plugin.get().getName() + "/c/$1";
+
+  static class Factory implements Panel.EntryPoint {
+    @Override
+    public void onLoad(Panel panel) {
+      panel.setWidget(new ChangeScreenPreferencePanel());
+    }
+  }
+
+  private Label savedLabel;
+  private Timer hideTimer;
+
+  ChangeScreenPreferencePanel() {
+    new RestApi("accounts").id("self").view("preferences")
+        .get(new AsyncCallback<AccountPreferencesInfo>() {
+          @Override
+          public void onSuccess(AccountPreferencesInfo result) {
+            display(result);
+          }
+
+          @Override
+          public void onFailure(Throwable caught) {
+            // never invoked
+          }
+        });
+  }
+
+  private void display(final AccountPreferencesInfo info) {
+    Label heading = new Label(Plugin.get().getName() + " plugin");
+    heading.setStyleName("smallHeading");
+    add(heading);
+    HorizontalPanel p = new HorizontalPanel();
+    add(p);
+
+    Label label = new Label("Change Screen:");
+    p.add(label);
+    label.getElement().getStyle().setMarginRight(5, Unit.PX);
+    label.getElement().getStyle().setMarginTop(2, Unit.PX);
+    final ListBox box = new ListBox();
+    p.add(box);
+    savedLabel = new Label("Saved");
+    savedLabel.getElement().getStyle().setMarginLeft(5, Unit.PX);
+    savedLabel.getElement().getStyle().setMarginTop(2, Unit.PX);
+    savedLabel.setVisible(false);
+    p.add(savedLabel);
+
+    box.addItem(DEFAULT, DEFAULT);
+    box.addItem(COOKBOOK, COOKBOOK);
+
+    String selected = DEFAULT;
+    if (info.urlAliases().containsKey(DEFAULT_URL_MATCH)) {
+      String token = info.urlAliases().get(DEFAULT_URL_MATCH);
+      if (token.equals(COOKBOOK_URL_TOKEN)) {
+        selected = COOKBOOK;
+      } else if (!token.equals(DEFAULT_URL_MATCH)) {
+        box.addItem(OTHER, OTHER);
+        selected = OTHER;
+      }
+    }
+
+    for (int i = 0; i < box.getItemCount(); i++) {
+      if (selected.equals(box.getValue(i))) {
+        box.setSelectedIndex(i);
+        break;
+      }
+    }
+
+    box.addChangeHandler(new ChangeHandler() {
+      @Override
+      public void onChange(ChangeEvent event) {
+        savedLabel.setVisible(false);
+        if (box.getSelectedValue().equals(OTHER)) {
+          return;
+        }
+
+        Map<String, String> urlAliases = info.urlAliases();
+        if (box.getSelectedValue().equals(COOKBOOK)) {
+          urlAliases.put(DEFAULT_URL_MATCH, COOKBOOK_URL_TOKEN);
+        } else {
+          urlAliases.remove(DEFAULT_URL_MATCH);
+        }
+        info.setUrlAliases(urlAliases);
+
+        new RestApi("accounts").id("self").view("preferences")
+            .put(info, new AsyncCallback<AccountPreferencesInfo>() {
+              @Override
+              public void onSuccess(AccountPreferencesInfo result) {
+                Plugin.get().refreshUserPreferences();
+                showSavedStatus();
+              }
+
+              @Override
+              public void onFailure(Throwable caught) {
+                // never invoked
+              }
+            });
+      }
+    });
+  }
+
+  private void showSavedStatus() {
+    if (hideTimer != null) {
+      hideTimer.cancel();
+      hideTimer = null;
+    }
+    savedLabel.setVisible(true);
+    hideTimer = new Timer() {
+      @Override
+      public void run() {
+        savedLabel.setVisible(false);
+        hideTimer = null;
+      }
+    };
+    hideTimer.schedule(1000);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/ChangeScreenStatusExtension.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/ChangeScreenStatusExtension.java
new file mode 100644
index 0000000..e0ceeed
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/ChangeScreenStatusExtension.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.cookbook.client;
+
+import com.google.gerrit.plugin.client.extension.Panel;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.InlineLabel;
+
+/**
+ * Extension for change screen that displays a status in the header bar.
+ */
+public class ChangeScreenStatusExtension extends FlowPanel {
+  static class Factory implements Panel.EntryPoint {
+    @Override
+    public void onLoad(Panel panel) {
+      panel.setWidget(new ChangeScreenStatusExtension());
+    }
+  }
+
+  ChangeScreenStatusExtension() {
+    getElement().getStyle().setPadding(5, Unit.PX);
+    add(new Image(CookBookPlugin.RESOURCES.greenCheck()));
+    add(new InlineLabel("OK"));
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/CookBookChangeScreen.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/CookBookChangeScreen.java
new file mode 100644
index 0000000..9a9bf86
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/CookBookChangeScreen.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.cookbook.client;
+
+import com.google.gerrit.plugin.client.screen.Screen;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.VerticalPanel;
+
+class CookBookChangeScreen extends VerticalPanel {
+  static class Factory implements Screen.EntryPoint {
+    @Override
+    public void onLoad(Screen screen) {
+      screen.setPageTitle("CookBook Change Screen");
+      screen.show(new CookBookChangeScreen());
+    }
+  }
+
+  CookBookChangeScreen() {
+    setStyleName("cookbook-panel");
+    add(new Label("TODO: implement this screen"));
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/CookBookChangeScreenExtension.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/CookBookChangeScreenExtension.java
new file mode 100644
index 0000000..d5d6e8a
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/CookBookChangeScreenExtension.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.cookbook.client;
+
+import com.google.gerrit.client.GerritUiExtensionPoint;
+import com.google.gerrit.client.info.ChangeInfo;
+import com.google.gerrit.plugin.client.extension.Panel;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwtexpui.clippy.client.CopyableLabel;
+
+/**
+ * Extension for change screen that displays the numeric change ID with
+ * copy-to-clipboard icon.
+ */
+public class CookBookChangeScreenExtension extends VerticalPanel {
+  static class Factory implements Panel.EntryPoint {
+    @Override
+    public void onLoad(Panel panel) {
+      panel.setWidget(new CookBookChangeScreenExtension(panel));
+    }
+  }
+
+  CookBookChangeScreenExtension(Panel panel) {
+    ChangeInfo change =
+        panel.getObject(GerritUiExtensionPoint.Key.CHANGE_INFO).cast();
+
+    Grid g = new Grid(1, 2);
+    g.addStyleName("infoBlock");
+    CellFormatter fmt = g.getCellFormatter();
+
+    g.setText(0, 0, "Numeric Change ID");
+    fmt.addStyleName(0, 0, "header");
+    g.setWidget(0, 1, new CopyableLabel(Integer.toString(change._number())));
+    add(g);
+
+    fmt.addStyleName(0, 0, "topmost");
+    fmt.addStyleName(0, 1, "topmost");
+    fmt.addStyleName(0, 0, "bottomheader");
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/CookBookPlugin.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/CookBookPlugin.java
index e9ace38..c51ec3c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/CookBookPlugin.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/CookBookPlugin.java
@@ -14,12 +14,53 @@
 
 package com.googlesource.gerrit.plugins.cookbook.client;
 
+import com.google.gerrit.client.GerritUiExtensionPoint;
+import com.google.gerrit.client.Resources;
 import com.google.gerrit.plugin.client.Plugin;
 import com.google.gerrit.plugin.client.PluginEntryPoint;
+import com.google.gerrit.plugin.client.extension.Panel;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Button;
 
 public class CookBookPlugin extends PluginEntryPoint {
+  public static final Resources RESOURCES = GWT.create(Resources.class);
+
   @Override
   public void onPluginLoad() {
     Plugin.get().screen("", new IndexScreen.Factory());
+    Plugin.get().screenRegex("c/(.*)", new CookBookChangeScreen.Factory());
+    Plugin.get().settingsScreen("preferences", "Food Preferences",
+        new FoodPreferencesScreen.Factory());
+    Plugin.get().panel(
+        GerritUiExtensionPoint.PREFERENCES_SCREEN_BOTTOM,
+        new ChangeScreenPreferencePanel.Factory());
+    Plugin.get().panel(GerritUiExtensionPoint.PROFILE_SCREEN_BOTTOM,
+        new CookBookProfileExtension.Factory());
+    Plugin.get().panel(
+        GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK,
+        new CookBookChangeScreenExtension.Factory());
+    Plugin.get().panel(GerritUiExtensionPoint.CHANGE_SCREEN_HEADER,
+        new ChangeScreenStatusExtension.Factory());
+    Plugin.get().panel(
+        GerritUiExtensionPoint.CHANGE_SCREEN_HEADER_RIGHT_OF_POP_DOWNS,
+        new BuildsDropDownPanel.Factory());
+    Plugin.get().panel(
+        GerritUiExtensionPoint.CHANGE_SCREEN_HEADER_RIGHT_OF_BUTTONS,
+        new Panel.EntryPoint() {
+          @Override
+          public void onLoad(Panel panel) {
+            Button b = new HighlightButton("Library-Compliance+1");
+            b.addClickHandler(new ClickHandler() {
+              @Override
+              public void onClick(ClickEvent event) {
+                Window.alert("TODO");
+              }
+            });
+            panel.setWidget(b);
+          }
+        });
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/CookBookProfileExtension.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/CookBookProfileExtension.java
new file mode 100644
index 0000000..e50427a
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/CookBookProfileExtension.java
@@ -0,0 +1,65 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.cookbook.client;
+
+import com.google.gerrit.client.GerritUiExtensionPoint;
+import com.google.gerrit.client.info.AccountInfo;
+import com.google.gerrit.plugin.client.extension.Panel;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
+import com.google.gwt.user.client.ui.VerticalPanel;
+
+/**
+ * Extension for the user profile screen.
+ */
+public class CookBookProfileExtension extends VerticalPanel {
+  static class Factory implements Panel.EntryPoint {
+    @Override
+    public void onLoad(Panel panel) {
+      AccountInfo accountInfo =
+          panel.getObject(GerritUiExtensionPoint.Key.ACCOUNT_INFO).cast();
+      panel.setWidget(new CookBookProfileExtension(accountInfo));
+    }
+  }
+
+  CookBookProfileExtension(AccountInfo accountInfo) {
+    Grid g = new Grid(3, 2);
+    g.addStyleName("infoBlock");
+    g.addStyleName("accountInfoBlock");
+    CellFormatter fmt = g.getCellFormatter();
+
+    // TODO: fetch employer and department via REST from server,
+    // e.g. GET /accounts/self/cookbook~info
+
+    g.setText(0, 0, "Employer");
+    fmt.addStyleName(0, 0, "header");
+    g.setText(0, 1, "CookBook Corporation");
+
+    g.setText(1, 0, "Department");
+    fmt.addStyleName(1, 0, "header");
+    g.setText(1, 1, "Cookies " + accountInfo.email());
+
+    g.setText(2, 0, "CookBook Email");
+    fmt.addStyleName(2, 0, "header");
+    g.setText(2, 1, accountInfo.username() != null
+        ? accountInfo.username() + "@cookbook.com"
+        : "N/A");
+    add(g);
+
+    fmt.addStyleName(0, 0, "topmost");
+    fmt.addStyleName(0, 1, "topmost");
+    fmt.addStyleName(2, 0, "bottomheader");
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/FoodPreferencesScreen.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/FoodPreferencesScreen.java
new file mode 100644
index 0000000..6ba46fd
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/FoodPreferencesScreen.java
@@ -0,0 +1,67 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.cookbook.client;
+
+import com.google.gerrit.plugin.client.screen.Screen;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.TextArea;
+import com.google.gwt.user.client.ui.VerticalPanel;
+
+class FoodPreferencesScreen extends VerticalPanel {
+  static class Factory implements Screen.EntryPoint {
+    @Override
+    public void onLoad(Screen screen) {
+      screen.setPageTitle("Settings");
+      screen.show(new FoodPreferencesScreen());
+    }
+  }
+
+  FoodPreferencesScreen() {
+    setStyleName("cookbook-panel");
+
+    Panel messagePanel = new VerticalPanel();
+    messagePanel.add(new Label("Food Allergies or Dietary Concerns:"));
+    TextArea txt = new TextArea();
+    txt.addKeyPressHandler(new KeyPressHandler() {
+      @Override
+      public void onKeyPress(final KeyPressEvent event) {
+        event.stopPropagation();
+      }
+    });
+    txt.setVisibleLines(12);
+    txt.setCharacterWidth(80);
+    txt.getElement().setPropertyBoolean("spellcheck", false);
+    messagePanel.add(txt);
+    add(messagePanel);
+
+    Button helloButton = new Button("Save");
+    helloButton.addStyleName("cookbook-helloButton");
+    helloButton.addClickHandler(new ClickHandler() {
+      @Override
+      public void onClick(final ClickEvent event) {
+        Window.alert("TODO: implement save");
+      }
+    });
+    add(helloButton);
+    helloButton.setEnabled(true);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/HighlightButton.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/HighlightButton.java
new file mode 100644
index 0000000..0af633f
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/HighlightButton.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.cookbook.client;
+
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+
+/**
+ * Highlight button for header line in change screen.
+ *
+ * This class can *only* be used within a panel that extends the header line of
+ * the change screen, but will not work standalone.
+ */
+public class HighlightButton extends Button {
+
+  public HighlightButton(String text) {
+    // Create Button with inner div. This is required to get proper styling
+    // in the context of the change screen.
+    super((new SafeHtmlBuilder()).openDiv()
+        .appendAttribute("style", "color: #fff;").append(text).closeDiv());
+    getElement().removeClassName("gwt-Button");
+    getElement().getStyle().setBackgroundColor("#4d90fe");
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/IndexScreen.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/IndexScreen.java
index 0edf949..774ec3d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/IndexScreen.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/IndexScreen.java
@@ -14,9 +14,7 @@
 
 package com.googlesource.gerrit.plugins.cookbook.client;
 
-import com.google.gerrit.client.Resources;
 import com.google.gerrit.plugin.client.screen.Screen;
-import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.Scheduler;
 import com.google.gwt.core.client.Scheduler.ScheduledCommand;
 import com.google.gwt.event.dom.client.ClickEvent;
@@ -45,14 +43,13 @@
 
   private TextBox usernameTxt;
   private TextArea greetingTxt;
-  private Resources RESOURCES = GWT.create(Resources.class);
 
   IndexScreen() {
     setStyleName("cookbook-panel");
 
     Panel labelImagePanel = new HorizontalPanel();
     Panel usernamePanel = new VerticalPanel();
-    Image img = new Image(RESOURCES.info());
+    Image img = new Image(CookBookPlugin.RESOURCES.info());
     img.setTitle("User to send greetings to");
     labelImagePanel.add(new Label("Username"));
     labelImagePanel.add(img);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/PopDownButton.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/PopDownButton.java
new file mode 100644
index 0000000..3cbe5d6
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/PopDownButton.java
@@ -0,0 +1,101 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.cookbook.client;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.FontWeight;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwtexpui.globalkey.client.GlobalKey;
+import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+
+/**
+ * Pop down button for header line in change screen.
+ *
+ * This class implements a button that on click opens a pop down panel with the
+ * provided widget, similar to the "Patch Sets", "Download" or "Included In" pop
+ * down panels on the change screen.
+ *
+ * This class can *only* be used within a panel that extends the header line of
+ * the change screen, but will not work standalone.
+ */
+public class PopDownButton extends Button {
+  private final Widget widget;
+  private PopupPanel popup;
+
+  public PopDownButton(String text, Widget widget) {
+    // Create Button with inner div. This is required to get proper styling
+    // in the context of the change screen.
+    super((new SafeHtmlBuilder()).openDiv().append(text).closeDiv());
+    getElement().removeClassName("gwt-Button");
+    addClickHandler(new ClickHandler() {
+      @Override
+      public void onClick(ClickEvent event) {
+        show();
+      }
+    });
+    this.widget = widget;
+  }
+
+  private void show() {
+    if (popup != null) {
+      getElement().getStyle().clearFontWeight();
+      popup.hide();
+      return;
+    }
+
+    final Widget relativeTo = getParent();
+    final PopupPanel p = new PopupPanel(true) {
+      @Override
+      public void setPopupPosition(int left, int top) {
+        top -= Document.get().getBodyOffsetTop();
+
+        int w = Window.getScrollLeft() + Window.getClientWidth();
+        int r = relativeTo.getAbsoluteLeft() + relativeTo.getOffsetWidth();
+        int right = w - r;
+        Style style = getElement().getStyle();
+        style.clearProperty("left");
+        style.setPropertyPx("right", right);
+        style.setPropertyPx("top", top);
+      }
+    };
+    Style popupStyle = p.getElement().getStyle();
+    popupStyle.setBorderWidth(0, Unit.PX);
+    popupStyle.setBackgroundColor("#EEEEEE");
+    p.addAutoHidePartner(getElement());
+    p.addCloseHandler(new CloseHandler<PopupPanel>() {
+      @Override
+      public void onClose(CloseEvent<PopupPanel> event) {
+        if (popup == p) {
+          getElement().getStyle().clearFontWeight();
+          popup = null;
+        }
+      }
+    });
+    p.add(widget);
+    p.showRelativeTo(relativeTo);
+    GlobalKey.dialog(p);
+    getElement().getStyle().setFontWeight(FontWeight.BOLD);
+    popup = p;
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/pluginprovider/HelloSshCommand.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/pluginprovider/HelloSshCommand.java
index b1da6de..853d04d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/cookbook/pluginprovider/HelloSshCommand.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/pluginprovider/HelloSshCommand.java
@@ -14,39 +14,64 @@
 
 package com.googlesource.gerrit.plugins.cookbook.pluginprovider;
 
+import com.google.gerrit.extensions.annotations.PluginData;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
 import com.google.inject.Inject;
 
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
+import org.kohsuke.args4j.Argument;
 
-/**
- * SSH command defined by dynamically registered plugins.
- *
- */
-@CommandMetaData(name = "print", description = "Print content of the plugin file")
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+/** SSH command defined by dynamically registered plugins. */
+@CommandMetaData(name = "cat", description = "Print content of plugin file")
 public final class HelloSshCommand extends SshCommand {
   private final String pluginName;
-  private final File pluginDir;
+  private final Path pluginDir;
+  private final Path dataDir;
+
+  @Argument(usage = "files in data directory to print")
+  private List<String> files = new ArrayList<>();
 
   @Inject
-  public HelloSshCommand(@PluginName String pluginName, SitePaths sitePaths) {
+  public HelloSshCommand(@PluginName String pluginName,
+      SitePaths sitePaths,
+      @PluginData Path dataDir) {
     this.pluginName = pluginName;
-    this.pluginDir = sitePaths.plugins_dir;
+    this.pluginDir = sitePaths.plugins_dir.normalize();
+    this.dataDir = dataDir.normalize();
   }
 
   @Override
   public void run() {
-    File pluginFile = new File(pluginDir, pluginName + ".ssh");
+    Path pluginPath = pluginDir.resolve(pluginName + ".ssh");
+    printOne(pluginPath);
+    for (String name : files) {
+      Path p = dataDir.resolve(name).normalize();
+      if (!p.startsWith(dataDir)) {
+        throw new RuntimeException(p + " is outside data directory " + dataDir);
+      }
+      printOne(p);
+    }
+  }
+
+  private void printOne(Path p) {
     try {
-      Files.copy(pluginFile.toPath(), out);
+      Files.copy(p, out);
     } catch (IOException e) {
-      throw new RuntimeException("Cannot read plugin content of " + pluginFile,
-          e);
+      try (PrintWriter w = new PrintWriter(err)) {
+        w.write("Error reading contents of ");
+        w.write(p.toAbsolutePath().toString());
+        w.write(": \n");
+        e.printStackTrace(w);
+      }
     }
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/pluginprovider/HelloSshPluginProvider.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/pluginprovider/HelloSshPluginProvider.java
index a7d7268..8ffd8c2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/cookbook/pluginprovider/HelloSshPluginProvider.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/pluginprovider/HelloSshPluginProvider.java
@@ -22,19 +22,25 @@
 
 import org.eclipse.jgit.internal.storage.file.FileSnapshot;
 
-import java.io.File;
+import java.nio.file.Path;
 
 /**
- * Dynamic provider of Gerrit plugins derived by *.ssh files under $GERRIT_SITE/plugins.
- *
+ * Dynamic provider of Gerrit plugins derived by *.ssh files under
+ * $GERRIT_SITE/plugins.
+ * <p>
  * Example of how to define a dynamic Gerrit plugin provider to register
  * a new plugin based on the content of *.ssh files.
- *
+ * <p>
  * This provider allows to define a Gerrit plugin by simply dropping a .ssh file
  * (e.g. hello.ssh) under $GERRIT_SITE/plugins.
+ * <p>
  * Once the file is created a new plugin is automatically loaded with the name
- * without extension of the .ssh file (e.g. hello) and a new 'cat' SSH command is
- * automatically available from the registered plugin.
+ * without extension of the .ssh file (e.g. hello) and a new 'cat' SSH command
+ * is automatically available from the registered plugin.
+ * <p>
+ * The 'cat' command will print the contents of the .ssh file, along with the
+ * contents of any arguments, resolved against the plugin's data directory
+ * $GERRIT_SITE/data/name.
  */
 public class HelloSshPluginProvider implements ServerPluginProvider {
   private static final String SSH_EXT = ".ssh";
@@ -46,22 +52,22 @@
   }
 
   @Override
-  public boolean handles(File srcFile) {
-    return srcFile.getName().endsWith(SSH_EXT);
+  public boolean handles(Path srcPath) {
+    return srcPath.getFileName().toString().endsWith(SSH_EXT);
   }
 
   @Override
-  public String getPluginName(File srcFile) {
-    String srcFileName = srcFile.getName();
-    return srcFileName.substring(0, srcFileName.length() - SSH_EXT.length());
+  public String getPluginName(Path srcPath) {
+    String name = srcPath.getFileName().toString();
+    return name.substring(0, name.length() - SSH_EXT.length());
   }
 
   @Override
-  public ServerPlugin get(File srcFile, FileSnapshot snapshot,
+  public ServerPlugin get(Path srcPath, FileSnapshot snapshot,
       PluginDescription pluginDescriptor) throws InvalidPluginException {
-    String name = getPluginName(srcFile);
+    String name = getPluginName(srcPath);
     return new ServerPlugin(name, pluginDescriptor.canonicalUrl,
-        pluginDescriptor.user, srcFile, snapshot,
+        pluginDescriptor.user, srcPath, snapshot,
         new HelloSshPluginContentScanner(name), pluginDescriptor.dataDir,
         getClass().getClassLoader());
   }
diff --git a/src/main/resources/Documentation/build.md b/src/main/resources/Documentation/build.md
index 0fc79fc..2379943 100644
--- a/src/main/resources/Documentation/build.md
+++ b/src/main/resources/Documentation/build.md
@@ -30,7 +30,6 @@
 
 To build the plugin, issue the following command:
 
-
 ```
   buck build plugin
 ```
@@ -41,6 +40,12 @@
   buck-out/gen/cookbook-plugin/cookbook-plugin.jar
 ```
 
+To execute the tests run:
+
+```
+  buck test
+```
+
 Build in Gerrit tree
 --------------------
 
@@ -63,6 +68,12 @@
   ./tools/eclipse/project.py
 ```
 
+To execute the tests run:
+
+```
+  buck test --include cookbook-plugin
+```
+
 Note that for compatibility reasons a Maven build is provided, but is considered
 to be deprecated and will be removed in a future version of this plugin.
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/cookbook/CookbookIT.java b/src/test/java/com/googlesource/gerrit/plugins/cookbook/CookbookIT.java
new file mode 100644
index 0000000..4c5bf0e
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/cookbook/CookbookIT.java
@@ -0,0 +1,32 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.cookbook;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PluginDaemonTest;
+
+import org.junit.Test;
+
+@NoHttpd
+public class CookbookIT extends PluginDaemonTest {
+
+  @Test
+  public void printTest() throws Exception {
+    assertThat(sshSession.exec("cookbook print")).isEqualTo("Hello world!\n");
+    assertThat(sshSession.hasError()).isFalse();
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/cookbook/CookbookTest.java b/src/test/java/com/googlesource/gerrit/plugins/cookbook/CookbookTest.java
deleted file mode 100644
index d6376ae..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/cookbook/CookbookTest.java
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (C) 2014 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.cookbook;
-
-import static org.junit.Assert.assertTrue;
-
-import org.junit.Test;
-
-public class CookbookTest {
-  @Test
-  public void cookbookTest() {
-    // Dummy test, only used to make sure the cookbook plugin gets compiled
-    // when running `buck test`, thus highlighting any compilation errors.
-    assertTrue(true);
-  }
-}