• Jobs
  • About
  • Perforce and FitNesse integration February 16, 2009

    Until a few releases back, the only solution of managing wiki page revisions FitNesse provided was with using zip files. Just recently FitNesse provided interfaces to enable integration with other revision control systems. It now comes with out of the box support for Subversion. Here I am providing support for another popular revision control system – Perforce.

    The steps to enable this integration are:

    Download Perforce Java APIs

    Download P4Package, a high level Java API for perforce. Put this jar file in your lib directory.

    Configure Perforce credentials

    You will need to create a perforce properties file that has all the information needed to access perforce. To do this create a file called p4.properties and place it with rest of your configuration files. If you have been following the layout as described in the Creating your Java project workspace post, then create this file in your testautomation/config directory.

    Here is a template you could use:

    #
    # P4 Properties
    #
    # Full path to the P4 executable
    p4.executable=C:\\Program Files\\Perforce\\P4.EXE
    
    # P4PORT to connect to.
    p4.port=public.perforce.com:1666
    
    # P4USER to run as.
    p4.user=john_doe
    
    # P4CLIENT to use.
    p4.client=jdoe_workspace
    
    # P4PASSWD to use for the P4USER, if one has been set.
    p4.password=mylittlesecret
    
    # Logfile to use, if uncommented.
    #p4.logfile=D:\\p4.log
    
    # Log level to use, if uncommented.
    #p4.log_level=split
    
    # SystemRoot and SystemDrive for a Windows machine. These are needed in order
    # for the package to resolve hostnames. Why? I have no idea. Send questions to
    # Bill.Gates@microsoft.com
    p4.sysroot=C:\\WINNT
    p4.sysdrive=C:
    
    # If uncommented, this will set the server timeout threshold (in milliseconds).
    #p4.threshold=30000
    

    Next time you start fitnesse, add the following argument in your java command: -Dp4.properties=config/p4.properties

    Alternatively, you could use the following ant target:

    <target name="start-fitnesse" description="spawns fitnesse server">
        <echo message="Starting FitNesse on port ${fitnesse.port}" />
        <java classname="fitnesse.FitNesse"
              fork="true"
              spawn="true"
              classpathref="testautomation.classpath">
            <arg line="-p ${fitnesse.port} -r ${fitnesse.root}"/>
            <jvmarg value="-Dp4.properties=config/p4.properties"/>
            <sysproperty key="COMMAND_PATTERN" value="java -Xms32M -Xmx192M -cp %p %m"/>
        </java>
    </target>
    

    Perforce Revision Control plugin

    Add two java classes under the package org.qaautomation.fitnesse.revisioncontrol.perforce that performs all the perforce actions:

    • PerforceRevisionController.java
    • PerforceState.java

    In the recommended project layout, the directory would be testautomation/java/src/org/qaautomation/fitnesse/revisioncontrol/perforce/.

    Here is the source code for the two files:

    PerforceRevisionController.java

    /*
     * Copyright (c) 2009, Rahul Poonekar
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
     * following conditions are met:
     *
     * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following
     *   disclaimer.
     * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
     *   following disclaimer in the documentation and/ or other materials provided with the distribution.
     * * Neither the name of qaautomation.org nor the names of its contributors may be used to endorse or promote
     *   products derived from this software without specific prior written permission.
     * 
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
     * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
     * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     */
    package org.qaautomation.fitnesse.revisioncontrol.perforce;
    
    import fitnesse.revisioncontrol.RevisionController;
    import fitnesse.revisioncontrol.RevisionControlException;
    import fitnesse.revisioncontrol.State;
    import fitnesse.wiki.PageData;
    import fitnesse.wiki.FileSystemPage;
    import fitnesse.wiki.VersionInfo;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.util.*;
    import java.text.SimpleDateFormat;
    
    import com.perforce.api.*;
    
    /**
     * FitNesse and Perforce integration.
     *
     * @author rahulpoonekar
     * @since Jan 25, 2009
     */
    public class PerforceRevisionController implements RevisionController {
        static boolean isWindows;
        static String clientRootPath;
        static String absoulteRootPath;
    
        final String DESC_PREFIX = "[CL ";
    
        Env p4env;
        boolean reversionControlEnabled = true;
        long lastServed = 0;
        static Change change = null;
    
        public PerforceRevisionController(){
            p4env = new Env(getSystemP4Properties());
    
            setup();
        }
    
        public PerforceRevisionController(final Properties properties) {
            p4env = new Env(properties);
    
            try {
                p4env.checkValidity();
            } catch (PerforceException e) {
                p4env = new Env(getSystemP4Properties());
                String p4port = p4env.getPort();
                System.out.println("Integrating with perforce [P4PORT=" + p4port + "]");
    
                setup();
            }
        }
    
        public State checkState(String... filePaths) throws RevisionControlException {
            PerforceState finalState = null;
            for (String fileName : filePaths) {
                FileEntry fileEntry = new FileEntry(p4env, getClientPath(fileName));
    
                fileEntry.sync();
                if (fileEntry.getHaveRev() > 0) {
                    if (finalState == null) {
                        if (isFileInAnOpenChangelist(fileEntry)) {
                            finalState = PerforceState.OPENED;
                        } else {
                            finalState = PerforceState.VERSIONED;
                        }
                    }
                } else {
                    if (finalState == null) {
                        if (isFileInAnOpenChangelist(fileEntry)) {
                            finalState = PerforceState.OPENED;
                        } else {
                            finalState = PerforceState.UNKNOWN;
                        }
                    }
                }
            }
    
            return finalState;
        }
    
        public void add(String... filePaths) throws RevisionControlException {
            if (filePaths.length > 0) {
                createChangelist(p4env);
    
                for (String fileName : filePaths) {
                    FileEntry fileEntry = new FileEntry(p4env, PerforceRevisionController.getClientPath(fileName));
    
                    try {
                        fileEntry.openForAdd(change);
                    } catch (Exception e) {
                        throw new RevisionControlException("Could not add " + fileEntry.getClientPath() + ".", e);
                    }
                }
            }
        }
    
        public void checkin(String... filePaths) throws RevisionControlException {
            // This is not recommended. You should review your changelist before checking it in!
            if (change != null) {
                try {
                    change.submit();
                } catch (SubmitException e) {
                    System.err.println("Checking in changes failed! " + e.getMessage());
                }
            }
        }
    
        public void checkout(String... filePaths) throws RevisionControlException {
            if (filePaths.length > 0) {
                createChangelist(p4env);
    
                for (String fileName : filePaths) {
                    FileEntry fileEntry = new FileEntry(p4env, PerforceRevisionController.getClientPath(fileName));
    
                    try {
                        fileEntry.openForEdit(false, change);
                    } catch (Exception e) {
                        throw new RevisionControlException("Could not open " + fileEntry.getClientPath() + " for edit.", e);
                    }
                }
            }
        }
    
        public void delete(String... filePaths) throws RevisionControlException {
            if (filePaths.length > 0) {
                createChangelist(p4env);
    
                for (String fileName : filePaths) {
                    FileEntry fileEntry = new FileEntry(p4env, PerforceRevisionController.getClientPath(fileName));
    
                    try {
                        fileEntry.openForDelete(change);
                    } catch (Exception e) {
                        throw new RevisionControlException("Could not add " + fileEntry.getClientPath() + ".", e);
                    }
                }
            }
        }
    
        public void revert(String... filePaths) throws RevisionControlException {
            if (filePaths.length > 0) {
                for (String fileName : filePaths) {
                    FileEntry fileEntry = new FileEntry(p4env, PerforceRevisionController.getClientPath(fileName));
                    fileEntry.sync();
    
                    if (fileEntry.revert()) {
                        try {
                            FileEntry.syncWorkspace(p4env, fileEntry.getDepotPath());
                        } catch (IOException e) {
                            fileEntry.sync();
                        }
                    }
                }
            }
        }
    
        public void update(String... filePaths) throws RevisionControlException {
            if (filePaths.length > 0) {
                // Get filePaths' directories!
                HashSet<String> dirPaths = new HashSet<String>();
                for (String fileName : filePaths) {
                    dirPaths.add(new File(getClientPath(fileName)).getParent());
                }
    
                // now sync the directories
                try {
                    for (String dirPath: dirPaths) {
                        FileEntry.syncWorkspace(p4env, dirPath + "/...");
                    }
                } catch (IOException e) {
                    // "Plan B" - Sync individual files.
                    for (String fileName : filePaths) {
                        FileEntry fileEntry = new FileEntry(p4env, getClientPath(fileName));
    
                        try {
                            FileEntry.syncWorkspace(p4env, fileEntry.getDepotPath());
                        } catch (IOException ioe) {
                            fileEntry.sync();
                        }
                    }
                }
            }
        }
    
        public void move(File src, File dest) throws RevisionControlException {
            createChangelist(p4env);
            try {
                Branch.integrate(
                        p4env,
                        getClientPath(src.getAbsolutePath()),
                        getClientPath(dest.getAbsolutePath()),
                        null,
                        change);
                delete(src.getAbsolutePath());
            } catch (PerforceException e) { /* Do nothing */ }
        }
    
        public PageData getRevisionData(FileSystemPage page, String label) throws Exception {
            int startAt = label.indexOf(DESC_PREFIX) + DESC_PREFIX.length();
            String changelistNumber;
            try {
                changelistNumber = label.substring(startAt, label.indexOf("]", startAt));
            } catch (Exception e) {
                System.out.println("Could not get revision data for " + page.getFileSystemPath() + " using label " + label);
                return page.getData();
            }
    
            Vector logs = FileEntry.getFileLog(p4env,
                    getClientPath(page.getFileSystemPath()) + File.separator + "content.txt");
    
            FileEntry fe = null;
            for (Object log: logs) {
                if (((FileEntry) log).getHeadChange() == Integer.parseInt(changelistNumber)) {
                    fe = (FileEntry) log;
                    break;
                }
            }
    
            if (fe == null) {
                return page.getData();
            }
    
            PageData data = new PageData(page.getData());
            data.setContent(fe.getFileContents());
            return data;
        }
    
        public Collection<VersionInfo> history(FileSystemPage page) throws Exception {
            ArrayList<VersionInfo> history = new ArrayList<VersionInfo>();
            if (!reversionControlEnabled) {
                return history;
            }
    
            if (lastServed == 0 || lastServed < System.currentTimeMillis() - 1000) {
                final String pageClientPath = getClientPath(page.getFileSystemPath());
                final String contentPath = pageClientPath + File.separator + "content.txt";
    
                Change&#91;&#93; pChanges = getChanges(p4env, contentPath, 10, p4env.getUser(), p4env.getClient(), "pending", false, null);
                Change&#91;&#93; sChanges = getChanges(p4env, contentPath, 10, null, null, "submitted", false, null);
                Change&#91;&#93; changes = new Change&#91;pChanges.length + sChanges.length&#93;;
                System.arraycopy(pChanges, 0, changes, 0, pChanges.length);
                System.arraycopy(sChanges, 0, changes, pChanges.length, sChanges.length);
    
                for (Change change: changes) {
                    StringBuffer description = new StringBuffer();
                    description.append(DESC_PREFIX).append(change.getNumber()).append("&#93; ").append(change.getDescription());
                    if (description.length() > 120) {
                        description.delete(117, description.length()-1).append(" ...");
                    }
    
                    VersionInfo info = new VersionInfo(
                            description.toString(),
                            change.getUser().getId(),
                            new SimpleDateFormat("yyyy/MM/dd").parse(change.getModtimeString())
                    );
                    history.add(info);
                }
    
                lastServed = System.currentTimeMillis();
                Utils.cleanUp();
            }
    
            return history;
        }
    
        public VersionInfo makeVersion(FileSystemPage page, PageData data) throws Exception {
            return new VersionInfo(page.getName());
        }
    
        public void removeVersion(FileSystemPage page, String versionName) throws Exception {
        }
    
        public void prune(FileSystemPage page) throws Exception {
        }
    
        public boolean isExternalReversionControlEnabled() {
            return reversionControlEnabled;
        }
    
        protected static void createChangelist(Env p4env) throws RevisionControlException {
            if (change == null) {
                try {
                    Change[] changes = getChanges(p4env, null, 10, p4env.getUser(), p4env.getClient(), "pending", false, null);
                    for (Change pchange : changes) {
                        if (pchange.getDescription().contains("FitNesse generated changelist - please review before checking in...")) {
                            change = pchange;
                            break;
                        }
                    }
                } catch (Exception e) {
                    // No separate changelist exists already. Should look for another one with a different title?
                    e.printStackTrace();
                }
                if (change != null) {
                    return;
                }
    
                change = new Change(p4env);
                change.setDescription("FitNesse generated changelist - please review before checking in...");
    
                try {
                    change.commit();
                } catch (CommitException e) {
                    throw new RevisionControlException("Could not create changelist.", e);
                }
            }
        }
    
        private void setup() {
            try {
                p4env.checkValidity();
    
                isWindows = Utils.isWindows();
    
                if (!isWindows) {
                    Client client = Client.getClient(p4env, p4env.getClient());
                    clientRootPath = client.getRoot();
                    try {
                        absoulteRootPath = new File(clientRootPath).getCanonicalPath();
                    } catch (IOException e) {
                        absoulteRootPath = new File(clientRootPath).getAbsolutePath();
                    }
                }
            } catch (PerforceException pe) {
                System.out.println("Could not validate perforce environment due to error [" +
                        pe.getMessage() + "]. Disabling perforce integration ...");
                reversionControlEnabled = false;
            }
        }
    
        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            Utils.cleanUp();
        }
    
        private Properties getSystemP4Properties() {
            String p4SystemProperty = System.getProperties().getProperty("p4.properties", "p4.properties");
            File p4PropertiesFile = new File(p4SystemProperty);
    
            // Ensure the p4.properties exists.
            if (!p4PropertiesFile.exists()) {
                throw new RuntimeException("Perforce properties could not be found." +
                        " Set p4.properties system property to its location!");
            }
    
            // If the property is set to a directory, then look for the file under it.
            if (p4PropertiesFile.isDirectory()) {
                p4PropertiesFile = new File(p4PropertiesFile, "p4.properties");
    
                if (!p4PropertiesFile.exists()) {
                    throw new RuntimeException("Perforce properties could not be found. Set p4.properties " +
                            "system property to the file location or the directory that contains p4.properties file!");
                }
            }
    
            // Load the perforce properties
            try {
                Properties p4props = new Properties();
                p4props.load(new FileInputStream(p4PropertiesFile));
                return p4props;
            } catch (IOException e) {
                throw new RuntimeException("Could not read " + p4PropertiesFile.getAbsolutePath(), e);
            }
        }
    
        protected static String getClientPath(String wikiPagePath) {
            String absoluteWikiPagePath;
            try {
                absoluteWikiPagePath = new File(wikiPagePath).getCanonicalPath();
            } catch (IOException e) {
                absoluteWikiPagePath = new File(wikiPagePath).getAbsolutePath();
            }
    
            if (isWindows) {
                return absoluteWikiPagePath;
            } else {
                return absoluteWikiPagePath.replaceFirst(absoulteRootPath, clientRootPath);
            }
        }
    
        private boolean isFileInAnOpenChangelist(FileEntry fileEntry) {
            fileEntry.sync();
    
            Vector v = FileEntry.getOpened(p4env, false);
            for(Object o : v) {
                if (fileEntry.getDepotPath() != null && fileEntry.getDepotPath().equals(((FileEntry) o).getDepotPath())) {
                    return true;
                }
            }
    
            return false;
        }
    
        @SuppressWarnings({"ConstantConditions"})
        public static Change[] getChanges(Env env, String path, int max, String p4user, String clientName, String type,
                                          boolean use_integs, String ufilter) throws PerforceException {
            int cmdlen = 12;
            String[] cmd;
            String tpath = path;
    
            if (p4user == null || p4user.equals("")) cmdlen = cmdlen - 2;
            if (clientName == null || clientName.equals("")) cmdlen = cmdlen - 2;
    
            if (use_integs) cmdlen++;
    
            if (null == tpath) tpath = "";
            if (tpath.trim().equals("")) cmdlen--;
    
            cmd = new String[cmdlen];
            if (!tpath.trim().equals("")) cmd[cmdlen-1] = tpath;
    
            cmd[0] = "p4";
            cmd[1] = "changes";
            cmd[2] = "-m";
            cmd[3] = String.valueOf(max);
            cmd[4] = "-l";
            cmd[5] = "-s";
            cmd[6] = (type==null || type.equals(""))? "submitted": type;
            int i = 7;
            if (use_integs) {
                cmd[i++] = "-i";
            }
            if (p4user != null && !p4user.equals("")) {
                cmd[i++] = "-u";
                cmd[i++] = p4user;
            }
            if (clientName != null && !clientName.equals("")) {
                cmd[i++] = "-c";
                cmd[i] = clientName;
            }
    
            Vector<Change> v = new Vector<Change>();
            Change[] chngs;
            StringTokenizer st;
            int num;
            String l, id, description = "";
            User user;
            Change c = null;
            String modtime, client_name;
    
            try {
                P4Process p = new P4Process(env);
                p.setRawMode(true);
                p.exec(cmd);
    
                while (null != (l = p.readLine())) {
                    if (l.startsWith("info: Change")) {
                        l = l.substring(6).trim();
    
                        st = new StringTokenizer(l);
                        if (! st.nextToken().equals("Change")) {
                            continue;
                        }
    
                        try {
                            num = Integer.parseInt(st.nextToken());
                        } catch (Exception ex) {
                            throw new PerforceException("Could not parse change number from line: "+l);
                        }
    
                        if (! st.nextToken().equals("on")) {
                            continue;
                        }
    
                        modtime = st.nextToken();
    
                        if (! st.nextToken().equals("by")) {
                            continue;
                        }
    
                        id = st.nextToken();
                        int pos = id.indexOf("@");
                        client_name = id.substring(pos+1);
                        id = id.substring(0,pos);
    
                        user = User.getUser(env,id);
    
                        if (null != c) {
                            c.setDescription(description);
                        }
                        description = "";
                        c = new Change(num);
                        c.setEnv(env);
                        c.setUser(user);
                        c.setClientName(client_name);
                        c.setModtimeString(modtime);
                        if (null == ufilter || id.equals(ufilter)) {
                            v.addElement(c);
                        }
                    } else {
                        l = l.substring(5).trim();
                        description += l + "\n";
                    }
                }
    
                if (null != c) {
                    c.setDescription(description);
                }
    
                p.close();
            } catch (IOException ex) {
                Debug.out(Debug.ERROR, ex);
            }
    
            chngs = new Change[v.size()];
            for (int j = 0; j < v.size(); j++) {
                chngs&#91;j&#93; = v.elementAt(j);
            }
    
            return chngs;
        }
    }
    &#91;/sourcecode&#93;
    <h4>PerforceState.java</h4>
    
    /*
     * Copyright (c) 2009, Rahul Poonekar
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
     * following conditions are met:
     *
     * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following
     *   disclaimer.
     * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
     *   following disclaimer in the documentation and/ or other materials provided with the distribution.
     * * Neither the name of qaautomation.org nor the names of its contributors may be used to endorse or promote
     *   products derived from this software without specific prior written permission.
     *
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
     * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
     * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     */
    package org.qaautomation.fitnesse.revisioncontrol.perforce;
    
    import fitnesse.revisioncontrol.RevisionControlOperation;
    import static fitnesse.revisioncontrol.RevisionControlOperation.*;
    import fitnesse.revisioncontrol.State;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * Maintains state of a file in Perforce.
     *
     * @author rahulpoonekar
     * @since Jan 26, 2009
     */
    public abstract class PerforceState implements State {
        String state;
    
        public static final PerforceState VERSIONED = new Versioned("Versioned");
        public static final PerforceState UNKNOWN = new Unknown("Unknown");
        public static final PerforceState DELETED = new Deleted("Deleted");
        public static final PerforceState OPENED = new Opened("Added");
        private static final Map<String, PerforceState> states = new HashMap<String, PerforceState>();
    
        static {
            states.put("Versioned", VERSIONED);
            states.put("Unknown", UNKNOWN);
            states.put("Deleted", DELETED);
            states.put("Added", OPENED);
        }
    
        protected PerforceState(String state) {
            this.state = state;
        }
        public static State instance(String state) {
            State revisionControlState = states.get(state);
            if (revisionControlState == null)
                revisionControlState = UNKNOWN;
            return revisionControlState;
        }
    
        public boolean isCheckedOut() {
            return true;
        }
    
        protected boolean contains(String msg, String searchString) {
            return msg.indexOf(searchString) != -1;
        }
    
        @Override
        public String toString() {
            return state;
        }
    
    }
    
    /* -----------  Derived Classes represents states  ----------- */
    
    class Versioned extends PerforceState {
        protected Versioned(String state) {
            super(state);
        }
    
        public RevisionControlOperation[] operations() {
            return new RevisionControlOperation[] { CHECKOUT, UPDATE, DELETE };
        }
    
        public boolean isNotUnderRevisionControl() {
            return false;
        }
    
        public boolean isCheckedIn() {
            return true;
        }
    
        public boolean isCheckedOut() {
            return false;
        }
    }
    
    class Unknown extends PerforceState {
        protected Unknown(String state) {
            super(state);
        }
    
        public RevisionControlOperation[] operations() {
            return new RevisionControlOperation[] { ADD };
        }
    
        public boolean isNotUnderRevisionControl() {
            return true;
        }
    
        public boolean isCheckedIn() {
            return false;
        }
    
        @Override
        public boolean isCheckedOut() {
            return false;
        }
    }
    
    class Deleted extends Versioned {
        protected Deleted(String state) {
            super(state);
        }
    
        public RevisionControlOperation[] operations() {
            return new RevisionControlOperation[] { REVERT }; //CHECKIN?
        }
    
        public boolean isNotUnderRevisionControl() {
            return false;
        }
    
        public boolean isCheckedIn() {
            return true;
        }
    }
    
    class Opened extends Versioned {
        protected Opened(String state) {
            super(state);
        }
    
        public RevisionControlOperation[] operations() {
            return new RevisionControlOperation[] { REVERT }; //CHECKIN?
        }
    
        public boolean isNotUnderRevisionControl() {
            return true;
        }
    
        public boolean isCheckedIn() {
            return false;
        }
    }
    

    Now compile your java classes.

    Enabling the plugin

    Add the following line in your plugins.properties file.

    RevisionController=org.qaautomation.fitnesse.revisioncontrol.perforce.PerforceRevisionController
    If this file does not exist, create it in the directory where you will run the java command to start FitNesse.

    Expected behavior

    Now when you start fitnesse you will notice a new “Revision Control” section in your left nav. The links here show up as below:

    • For a file that is not checked in, you will get the link: Add
    • For a file that is checked in, you will get the links: Checkout, Update and Delete
    • And for a file that is checked out, you will see: Revert

    If you click Add, Checkout or Delete for a wiki page, you will see its effect in a new pending changelist with the description as FitNesse generated changelist – please review before checking in…. Always review your changelist, change the description and then submit it. Currently, submission of a changelist is disabled from within FitNesse with this plugin.

    Hope this helps!

    Posted by Rahul Poonekar in : FitNesse

    2 responses to “Perforce and FitNesse integration”

    1. Wil Morrison says:

      Hi,

      I was wondering if you could help me out. I’m trying to get fitnesse to recognize my PerforceRevisionController. Because of the line in the plugins.properties file, I keep getting ClassNotFoundException when I click on run.bat. I’ve tried to set my classpath in run.bat but I still have no luck. Is there a specific place that I need to place PerforceRevisionController.class, Versioned.class, Deleted.class, etc.? Do I need to make a jar file out of these files? Is there a specific folder structure that I need to compile the source files under? I really need some assistance.

    2. Wil Morrison says:

      Nevermind, I figured it out. I actually had to include the compiled files in “fitnesse.jar” in the correct classpath.

    Leave a Reply

    Your email address will not be published. Required fields are marked *