fix environment for saucy binary jobs

fix environment for saucy binary jobs

<project>
  <actions/>
  <description>Generated at @ESCAPE(now_str) from template '@ESCAPE(template_name)'</description>
@(SNIPPET(
    'log-rotator',
    days_to_keep=180,
    num_to_keep=30,
))@
  <keepDependencies>false</keepDependencies>
  <properties>
@[if job_priority is not None]@
@(SNIPPET(
    'property_job-priority',
    priority=job_priority,
))@
@[end if]@
@(SNIPPET(
    'property_requeue-job',
))@
  </properties>
@(SNIPPET(
    'scm_null',
))@
  <assignedNode>buildslave</assignedNode>
  <canRoam>false</canRoam>
  <disabled>false</disabled>
  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
  <blockBuildWhenUpstreamBuilding>true</blockBuildWhenUpstreamBuilding>
  <triggers>
@[if upstream_projects]@
@(SNIPPET(
    'trigger_reverse-build',
    upstream_projects=upstream_projects,
))@
@[end if]@
  </triggers>
  <concurrentBuild>false</concurrentBuild>
  <builders>
@(SNIPPET(
    'builder_system-groovy_verify-upstream',
))@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        'echo "# BEGIN SECTION: Clone custom rosdistro"',
        'rm -fr rosdistro',
        'git clone -b rep143 https://github.com/ros-infrastructure/rosdistro.git rosdistro',
        'echo "# END SECTION"',
        '',
        'echo "# BEGIN SECTION: Clone ros_buildfarm"',
        'rm -fr ros_buildfarm',
        'git clone https://github.com/ros-infrastructure/ros_buildfarm.git ros_buildfarm',
        'echo "# END SECTION"',
    ]),
))@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        '# generate key files',
        'echo "# BEGIN SECTION: Generate key files"',
    ] + script_generating_key_files + [
        'echo "# END SECTION"',
    ]),
))@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        '# generate Dockerfile, build and run it',
        '# generating the Dockerfile for the actual binarydeb task',
        'echo "# BEGIN SECTION: Generate Dockerfile - binarydeb task"',
        'mkdir -p $WORKSPACE/docker_generating_docker',
        'export PYTHONPATH=$WORKSPACE/ros_buildfarm:$PYTHONPATH',
        '$WORKSPACE/ros_buildfarm/scripts/release/run_binarydeb_job.py' +
        ' --rosdistro-index-url ' + rosdistro_index_url +
        ' ' + rosdistro_name +
        ' ' + pkg_name +
        ' ' + os_name +
        ' ' + os_code_name +
        ' ' + arch +
        ' ' + ' '.join(apt_mirror_args) +
        ' --binarydeb-dir $WORKSPACE/binarydeb' +
        ' --dockerfile-dir $WORKSPACE/docker_generating_docker' +
        (' --append-timestamp' if append_timestamp else ''),
        'echo "# END SECTION"',
        '',
        'echo "# BEGIN SECTION: Build Dockerfile - binarydeb task"',
        'cd $WORKSPACE/docker_generating_docker',
        'docker build -t binarydeb .',
        'echo "# END SECTION"',
        '',
        'echo "# BEGIN SECTION: Run Dockerfile - binarydeb task"',
        'rm -fr $WORKSPACE/binarydeb',
        'mkdir -p $WORKSPACE/binarydeb',
        'mkdir -p $WORKSPACE/docker_build_binarydeb',
        'docker run' +
        ' -v $WORKSPACE/ros_buildfarm:/tmp/ros_buildfarm:ro' +
        ' -v $WORKSPACE/rosdistro:/tmp/rosdistro:ro' +
        ' -v $WORKSPACE/binarydeb:/tmp/binarydeb' +
        ' -v $WORKSPACE/docker_build_binarydeb:/tmp/docker_build_binarydeb' +
        ' binarydeb',
        'echo "# END SECTION"',
    ]),
))@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        'echo "# BEGIN SECTION: Build Dockerfile - build binarydeb"',
        '# build and run build_binarydeb Dockerfile',
        'cd $WORKSPACE/docker_build_binarydeb',
        'docker build -t build_binarydeb .',
        'echo "# END SECTION"',
        '',
        'echo "# BEGIN SECTION: Run Dockerfile - build binarydeb"',
        '# -e=HOME= is required to set a reasonable HOME for the user (not /)',
        '# otherwise apt-src will fail',
        'docker run' +
        ' -e=HOME=' +
        ' --net=host' +
        ' -v $WORKSPACE/ros_buildfarm:/tmp/ros_buildfarm:ro' +
        ' -v $WORKSPACE/binarydeb:/tmp/binarydeb' +
        ' build_binarydeb',
        'echo "# END SECTION"',
    ]),
))@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        '# generate Dockerfile, build and run it',
        '# trying to install the built binarydeb',
        'echo "# BEGIN SECTION: Generate Dockerfile - install"',
        'mkdir -p $WORKSPACE/docker_install_binarydeb',
        'export PYTHONPATH=$WORKSPACE/ros_buildfarm:$PYTHONPATH',
        '$WORKSPACE/ros_buildfarm/scripts/release/create_binarydeb_install_task_generator.py' +
        ' ' + os_name +
        ' ' + os_code_name +
        ' ' + arch +
        ' ' + ' '.join(apt_mirror_args) +
        ' --binarydeb-dir $WORKSPACE/binarydeb' +
        ' --dockerfile-dir $WORKSPACE/docker_install_binarydeb',
        'echo "# END SECTION"',
        '',
        'echo "# BEGIN SECTION: Build Dockerfile - install"',
        'cd $WORKSPACE/docker_install_binarydeb',
        'docker build -t install_binarydeb .',
        'echo "# END SECTION"',
        '',
        'echo "# BEGIN SECTION: Run Dockerfile - install"',
        'docker run' +
        ' -v $WORKSPACE/binarydeb:/tmp/binarydeb:ro' +
        ' install_binarydeb',
        'echo "# END SECTION"',
    ]),
))@
@(SNIPPET(
    'builder_publish-over-ssh',
    config_name='repo',
    remote_directory='%s/${JOB_NAME}__${BUILD_NUMBER}' % os_code_name,
    source_files=binarydeb_files,
    remove_prefix='binarydeb',
))@
@(SNIPPET(
    'builder_parameterized-trigger',
    project=import_package_job_name,
    parameters='\n'.join([
        'subfolder=%s/${JOB_NAME}__${BUILD_NUMBER}' % os_code_name,
        'debian_package_name=%s' % debian_package_name]),
))@
  </builders>
  <publishers>
@(SNIPPET(
    'publisher_description-setter',
    regexp="Package '[^']+' version: (\S+)",
    regexp_for_failed='',
))@
@[if notify_maintainers]@
@(SNIPPET(
    'publisher_groovy-postbuild_maintainer-notification',
))@
@[end if]@
@(SNIPPET(
    'publisher_mailer',
    recipients=notify_emails,
    dynamic_recipients=maintainer_emails,
    send_to_individuals=False,
))@
  </publishers>
  <buildWrappers>
@[if timeout_minutes is not None]@
@(SNIPPET(
    'build-wrapper_build-timeout',
    timeout_minutes=timeout_minutes,
))@
@[end if]@
@(SNIPPET(
    'build-wrapper_timestamper',
))@
@(SNIPPET(
    'build-wrapper_ssh-agent_credential-id',
))@
  </buildWrappers>
</project>

<project>
  <actions/>
  <description>Generated at @ESCAPE(now_str) from template '@ESCAPE(template_name)'</description>
@(SNIPPET(
    'log-rotator',
    days_to_keep=180,
    num_to_keep=30,
))@
  <keepDependencies>false</keepDependencies>
  <properties>
@[if job_priority is not None]@
@(SNIPPET(
    'property_job-priority',
    priority=job_priority,
))@
@[end if]@
@(SNIPPET(
    'property_requeue-job',
))@
  </properties>
@(SNIPPET(
    'scm_null',
))@
  <assignedNode>buildslave</assignedNode>
  <canRoam>false</canRoam>
  <disabled>false</disabled>
  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
  <blockBuildWhenUpstreamBuilding>true</blockBuildWhenUpstreamBuilding>
  <triggers>
@[if upstream_projects]@
@(SNIPPET(
    'trigger_reverse-build',
    upstream_projects=upstream_projects,
))@
@[end if]@
  </triggers>
  <concurrentBuild>false</concurrentBuild>
  <builders>
@(SNIPPET(
    'builder_system-groovy_verify-upstream',
))@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        'echo "# BEGIN SECTION: Clone custom rosdistro"',
        'rm -fr rosdistro',
        'git clone -b rep143 https://github.com/ros-infrastructure/rosdistro.git rosdistro',
        'echo "# END SECTION"',
        '',
        'echo "# BEGIN SECTION: Clone ros_buildfarm"',
        'rm -fr ros_buildfarm',
        'git clone https://github.com/ros-infrastructure/ros_buildfarm.git ros_buildfarm',
        'echo "# END SECTION"',
    ]),
))@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        '# generate key files',
        'echo "# BEGIN SECTION: Generate key files"',
    ] + script_generating_key_files + [
        'echo "# END SECTION"',
    ]),
))@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        '# generate Dockerfile, build and run it',
        '# generating the Dockerfile for the actual binarydeb task',
        'echo "# BEGIN SECTION: Generate Dockerfile - binarydeb task"',
        'mkdir -p $WORKSPACE/docker_generating_docker',
        'export PYTHONPATH=$WORKSPACE/ros_buildfarm:$PYTHONPATH',
        '$WORKSPACE/ros_buildfarm/scripts/release/run_binarydeb_job.py' +
        ' --rosdistro-index-url ' + rosdistro_index_url +
        ' ' + rosdistro_name +
        ' ' + pkg_name +
        ' ' + os_name +
        ' ' + os_code_name +
        ' ' + arch +
        ' ' + ' '.join(apt_mirror_args) +
        ' --binarydeb-dir $WORKSPACE/binarydeb' +
        ' --dockerfile-dir $WORKSPACE/docker_generating_docker' +
        (' --append-timestamp' if append_timestamp else ''),
        'echo "# END SECTION"',
        '',
        'echo "# BEGIN SECTION: Build Dockerfile - binarydeb task"',
        'cd $WORKSPACE/docker_generating_docker',
        'docker build -t binarydeb .',
        'echo "# END SECTION"',
        '',
        'echo "# BEGIN SECTION: Run Dockerfile - binarydeb task"',
        'rm -fr $WORKSPACE/binarydeb',
        'mkdir -p $WORKSPACE/binarydeb',
        'mkdir -p $WORKSPACE/docker_build_binarydeb',
        'docker run' +
        ' -v $WORKSPACE/ros_buildfarm:/tmp/ros_buildfarm:ro' +
        ' -v $WORKSPACE/rosdistro:/tmp/rosdistro:ro' +
        ' -v $WORKSPACE/binarydeb:/tmp/binarydeb' +
        ' -v $WORKSPACE/docker_build_binarydeb:/tmp/docker_build_binarydeb' +
        ' binarydeb',
        'echo "# END SECTION"',
    ]),
))@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        'echo "# BEGIN SECTION: Build Dockerfile - build binarydeb"',
        '# build and run build_binarydeb Dockerfile',
        'cd $WORKSPACE/docker_build_binarydeb',
        'docker build -t build_binarydeb .',
        'echo "# END SECTION"',
        '',
        'echo "# BEGIN SECTION: Run Dockerfile - build binarydeb"',
        'docker run' +
        ' --net=host' +
        ' -v $WORKSPACE/ros_buildfarm:/tmp/ros_buildfarm:ro' +
        ' -v $WORKSPACE/binarydeb:/tmp/binarydeb' +
        ' build_binarydeb',
        'echo "# END SECTION"',
    ]),
))@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        '# generate Dockerfile, build and run it',
        '# trying to install the built binarydeb',
        'echo "# BEGIN SECTION: Generate Dockerfile - install"',
        'mkdir -p $WORKSPACE/docker_install_binarydeb',
        'export PYTHONPATH=$WORKSPACE/ros_buildfarm:$PYTHONPATH',
        '$WORKSPACE/ros_buildfarm/scripts/release/create_binarydeb_install_task_generator.py' +
        ' ' + os_name +
        ' ' + os_code_name +
        ' ' + arch +
        ' ' + ' '.join(apt_mirror_args) +
        ' --binarydeb-dir $WORKSPACE/binarydeb' +
        ' --dockerfile-dir $WORKSPACE/docker_install_binarydeb',
        'echo "# END SECTION"',
        '',
        'echo "# BEGIN SECTION: Build Dockerfile - install"',
        'cd $WORKSPACE/docker_install_binarydeb',
        'docker build -t install_binarydeb .',
        'echo "# END SECTION"',
        '',
        'echo "# BEGIN SECTION: Run Dockerfile - install"',
        'docker run' +
        ' -v $WORKSPACE/binarydeb:/tmp/binarydeb:ro' +
        ' install_binarydeb',
        'echo "# END SECTION"',
    ]),
))@
@(SNIPPET(
    'builder_publish-over-ssh',
    config_name='repo',
    remote_directory='%s/${JOB_NAME}__${BUILD_NUMBER}' % os_code_name,
    source_files=binarydeb_files,
    remove_prefix='binarydeb',
))@
@(SNIPPET(
    'builder_parameterized-trigger',
    project=import_package_job_name,
    parameters='\n'.join([
        'subfolder=%s/${JOB_NAME}__${BUILD_NUMBER}' % os_code_name,
        'debian_package_name=%s' % debian_package_name]),
))@
  </builders>
  <publishers>
@(SNIPPET(
    'publisher_description-setter',
    regexp="Package '[^']+' version: (\S+)",
    regexp_for_failed='',
))@
@[if notify_maintainers]@
@(SNIPPET(
    'publisher_groovy-postbuild_maintainer-notification',
))@
@[end if]@
@(SNIPPET(
    'publisher_mailer',
    recipients=notify_emails,
    dynamic_recipients=maintainer_emails,
    send_to_individuals=False,
))@
  </publishers>
  <buildWrappers>
@[if timeout_minutes is not None]@
@(SNIPPET(
    'build-wrapper_build-timeout',
    timeout_minutes=timeout_minutes,
))@
@[end if]@
@(SNIPPET(
    'build-wrapper_timestamper',
))@
@(SNIPPET(
    'build-wrapper_ssh-agent_credential-id',
))@
  </buildWrappers>
</project>


use custom rosdistro_index_url

use custom rosdistro_index_url

Signed-off-by: Mikael Arguedas <a16f295d1139a1392bb19262902c32cf3e999ce4@gmail.com>

@(TEMPLATE(
    'snippet/add_generated_comment.Dockerfile.em',
    user_name=user_name,
    tag_name=tag_name,
    source_template_name=template_name,
))@
@(TEMPLATE(
    'snippet/from_base_image.Dockerfile.em',
    template_packages=template_packages,
    os_name=os_name,
    os_code_name=os_code_name,
    arch=arch,
    base_image=base_image,
    maintainer_name=maintainer_name,
))@
@(TEMPLATE(
    'snippet/setup_tzdata.Dockerfile.em',
    os_name=os_name,
    os_code_name=os_code_name,
))@
@{
template_dependencies = [
    'dirmngr',
    'gnupg2',
    'lsb-release',
    'wget',
]
# add 'python3-pip' to 'template_dependencies' if pip dependencies are declared
if 'pip3_install' in locals():
    if isinstance(pip3_install, list) and pip3_install != []:
        template_dependencies.append('python3-pip')
}@
@(TEMPLATE(
    'snippet/install_upstream_package_list.Dockerfile.em',
    packages=template_dependencies,
    upstream_packages=upstream_packages if 'upstream_packages' in locals() else [],
))@
@
# setup ros2 keys
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654

# setup sources.list
RUN echo "deb http://packages.ros.org/ros2-testing/ubuntu `lsb_release -sc` main" > /etc/apt/sources.list.d/ros2-testing.list

@(TEMPLATE(
    'snippet/install_ros_bootstrap_tools.Dockerfile.em',
    ros_version=ros_version,
))@

# setup environment
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8

@[if 'pip3_install' in locals()]@
@[  if pip3_install]@
# install python packages
RUN pip3 install -U \
    @(' \\\n    '.join(pip3_install))@

@[  end if]@
@[end if]@

# install ros2 packages
ENV ROS_DISTRO @ros2distro_name
RUN mkdir -p /opt/ros/$ROS_DISTRO
ARG ROS2_BINARY_URL=@ros2_binary_url
RUN wget -q $ROS2_BINARY_URL -O - | \
    tar -xj --strip-components=1 -C /opt/ros/$ROS_DISTRO

# install setup files
RUN apt-get update && apt-get install -q -y \
    ros-$ROS_DISTRO-ros-workspace \
    && rm -rf /var/lib/apt/lists/*

# bootstrap rosdep
@[  if 'rosdistro_index_url' in rosdep]@
ENV ROSDISTRO_INDEX_URL @(rosdep['rosdistro_index_url'])
@[  end if]@
RUN rosdep init

@[if 'rosdep_override' in locals()]@
# add custom rosdep rule files
@[  for rule_file in rosdep_override]@
COPY @rule_file /etc/ros/rosdep/
RUN echo "yaml file:///etc/ros/rosdep/@rule_file" | \
    cat - /etc/ros/rosdep/sources.list.d/20-default.list > temp && \
    mv temp /etc/ros/rosdep/sources.list.d/20-default.list
@[  end for]@
RUN rosdep update

@[end if]@
@
@[if 'rosdep' in locals()]@
@{
if 'path' not in rosdep:
  rosdep['path']='/opt/ros/$ROS_DISTRO/share'
}@
# install dependencies
RUN . /opt/ros/$ROS_DISTRO/setup.sh \
    && apt-get update \
    && rosdep install -y \
    --from-paths @(rosdep['path']) \
    --ignore-src \
    --skip-keys " \
      @(' \\\n      '.join(rosdep['skip_keys']))@ " \
    && rm -rf /var/lib/apt/lists/*

@[end if]@
@
# FIXME Remove this once rosdep detects ROS 2 packages https://github.com/ros-infrastructure/rosdep/issues/660
# ignore installed rosdep keys
ENV ROS_PACKAGE_PATH /opt/ros/$ROS_DISTRO/share

@[if 'entrypoint_name' in locals()]@
@[  if entrypoint_name]@
@{
entrypoint_file = entrypoint_name.split('/')[-1]
}@
# setup entrypoint
COPY ./@entrypoint_file /

ENTRYPOINT ["/@entrypoint_file"]
@[  end if]@
@[end if]@
@{
cmds = [
'bash',
]
}@
CMD ["@(' && '.join(cmds))"]

@(TEMPLATE(
    'snippet/add_generated_comment.Dockerfile.em',
    user_name=user_name,
    tag_name=tag_name,
    source_template_name=template_name,
))@
@(TEMPLATE(
    'snippet/from_base_image.Dockerfile.em',
    template_packages=template_packages,
    os_name=os_name,
    os_code_name=os_code_name,
    arch=arch,
    base_image=base_image,
    maintainer_name=maintainer_name,
))@
@(TEMPLATE(
    'snippet/setup_tzdata.Dockerfile.em',
    os_name=os_name,
    os_code_name=os_code_name,
))@
@{
template_dependencies = [
    'dirmngr',
    'gnupg2',
    'lsb-release',
    'wget',
]
# add 'python3-pip' to 'template_dependencies' if pip dependencies are declared
if 'pip3_install' in locals():
    if isinstance(pip3_install, list) and pip3_install != []:
        template_dependencies.append('python3-pip')
}@
@(TEMPLATE(
    'snippet/install_upstream_package_list.Dockerfile.em',
    packages=template_dependencies,
    upstream_packages=upstream_packages if 'upstream_packages' in locals() else [],
))@
@
# setup ros2 keys
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654

# setup sources.list
RUN echo "deb http://packages.ros.org/ros2-testing/ubuntu `lsb_release -sc` main" > /etc/apt/sources.list.d/ros2-testing.list

@(TEMPLATE(
    'snippet/install_ros_bootstrap_tools.Dockerfile.em',
    ros_version=ros_version,
))@

# setup environment
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8

# bootstrap rosdep
RUN rosdep init \
    && rosdep update

@[if 'pip3_install' in locals()]@
@[  if pip3_install]@
# install python packages
RUN pip3 install -U \
    @(' \\\n    '.join(pip3_install))@

@[  end if]@
@[end if]@

# install ros2 packages
ENV ROS_DISTRO @ros2distro_name
RUN mkdir -p /opt/ros/$ROS_DISTRO
ARG ROS2_BINARY_URL=@ros2_binary_url
RUN wget -q $ROS2_BINARY_URL -O - | \
    tar -xj --strip-components=1 -C /opt/ros/$ROS_DISTRO

# install setup files
RUN apt-get update && apt-get install -q -y \
    ros-$ROS_DISTRO-ros-workspace \
    && rm -rf /var/lib/apt/lists/*

@[if 'rosdep_override' in locals()]@
# add custom rosdep rule files
@[  for rule_file in rosdep_override]@
COPY @rule_file /etc/ros/rosdep/
RUN echo "yaml file:///etc/ros/rosdep/@rule_file" | \
    cat - /etc/ros/rosdep/sources.list.d/20-default.list > temp && \
    mv temp /etc/ros/rosdep/sources.list.d/20-default.list
@[  end for]@
RUN rosdep update

@[end if]@
@
@[if 'rosdep' in locals()]@
@{
if 'path' not in rosdep:
  rosdep['path']='/opt/ros/$ROS_DISTRO/share'
}@
# install dependencies
RUN . /opt/ros/$ROS_DISTRO/setup.sh \
    && apt-get update \
    && rosdep install -y \
    --from-paths @(rosdep['path']) \
    --ignore-src \
    --skip-keys " \
      @(' \\\n      '.join(rosdep['skip_keys']))@ " \
    && rm -rf /var/lib/apt/lists/*

@[end if]@
@
# FIXME Remove this once rosdep detects ROS 2 packages https://github.com/ros-infrastructure/rosdep/issues/660
# ignore installed rosdep keys
ENV ROS_PACKAGE_PATH /opt/ros/$ROS_DISTRO/share

@[if 'entrypoint_name' in locals()]@
@[  if entrypoint_name]@
@{
entrypoint_file = entrypoint_name.split('/')[-1]
}@
# setup entrypoint
COPY ./@entrypoint_file /

ENTRYPOINT ["/@entrypoint_file"]
@[  end if]@
@[end if]@
@{
cmds = [
'bash',
]
}@
CMD ["@(' && '.join(cmds))"]


simplify logic

simplify logic

See https://github.com/ros-infrastructure/ros_buildfarm/commit/f96ee6e1578838c2ba5763fbcc1de1d8ea33a514#commitcomment-16663472

@[if arch in ['i386', 'armhf', 'arm64']]@
FROM osrf/@(os_name)_@arch:@os_code_name
@[else]@
FROM @os_name:@os_code_name
@[end if]@

@[if os_name == 'ubuntu' and arch in ['i386', 'armhf', 'arm64']]@
@[if arch == 'i386']@
FROM osrf/ubuntu_32bit:@os_code_name
@[else]@
@[if arch == 'armhf']@
FROM osrf/ubuntu_armhf:@os_code_name
@[else]@
FROM osrf/ubuntu_arm64:@os_code_name
@[end if]@
@[end if]@
@[else]@
FROM @os_name:@os_code_name
@[end if]@


remove unneeded code from tests

remove unneeded code from tests

describe "rest", ->

  adapter = null
  session = null

  beforeEach ->
    require('./_shared').setupRest.apply(this)
    adapter = @adapter
    session = @session

  context 'one->many', ->

    beforeEach ->
      class @Post extends Ep.Model
        title: Ep.attr('string')
      @App.Post = @Post

      class @Comment extends Ep.Model
        message: Ep.attr('string')
        post: Ep.belongsTo(@Post)
      @App.Comment = @Comment

      @Post.reopen
        comments: Ep.hasMany(@Comment)

      @container.register 'model:post', @Post
      @container.register 'model:comment', @Comment

    it 'loads lazily', ->
      adapter.r['GET:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: [2]}
      adapter.r['GET:/comments/2'] = comments: {id: 2, message: 'first', post: 1}

      session.load('post', 1).then (post) ->
        expect(adapter.h).to.eql(['GET:/posts/1'])
        expect(post.id).to.eq("1")
        expect(post.title).to.eq('mvcc ftw')
        expect(post.comments.length).to.eq(1)
        comment = post.comments.firstObject
        expect(comment.message).to.be.undefined

        post.comments.firstObject.then ->
          expect(adapter.h).to.eql(['GET:/posts/1', 'GET:/comments/2'])
          expect(comment.message).to.eq('first')
          expect(comment.post.isEqual(post)).to.be.true

    it 'creates', ->
      adapter.r['POST:/posts'] = -> posts: {client_id: post.clientId, id: 1, title: 'topological sort', comments: []}
      adapter.r['POST:/comments'] = (url, type, hash) ->
        expect(hash.data.comment.post).to.eq(1)
        return comments: {client_id: comment.clientId, id: 2, message: 'seems good', post: 1}

      post = session.create('post')
      post.title = 'topological sort'

      comment = session.create('comment')
      comment.message = 'seems good'
      comment.post = post

      expect(post.comments.firstObject).to.eq(comment)

      session.flush().then ->
        expect(post.id).to.not.be.null
        expect(post.isNew).to.be.false
        expect(post.title).to.eq('topological sort')
        expect(comment.id).to.not.be.null
        expect(comment.message).to.eq('seems good')
        expect(comment.post).to.eq(post)
        expect(comment.post.id).to.eq("1")
        expect(post.comments.firstObject).to.eq(comment)
        expect(adapter.h).to.eql(['POST:/posts', 'POST:/comments'])

    it 'creates child', ->
      adapter.r['POST:/comments'] = -> comments: {client_id: comment.clientId, id: 2, message: 'new child', post: 1}

      session.merge @Post.create(id: "1", title: 'parent');

      comment = null

      session.load(@Post, 1).then (post) ->
        comment = session.create('comment', message: 'new child')
        comment.post = post
        expect(post.comments.toArray()).to.eql([comment])
        session.flush().then ->
          expect(post.comments.toArray()).to.eql([comment])
          expect(comment.message).to.eq('new child')
          expect(adapter.h).to.eql(['POST:/comments'])

    it 'create followed by delete does not hit server', ->
      session.merge @Post.create(id: "1", title: 'parent');

      comment = null

      session.load(@Post, 1).then (post) ->
        comment = session.create('comment', message: 'new child')
        comment.post = post
        session.deleteModel comment
        session.flush().then ->
          expect(adapter.h).to.eql([])
          expect(comment.isDeleted).to.be.true

    it 'updates parent, updates child, and saves sibling', ->
      adapter.r['PUT:/posts/1'] = -> post: {id: 1, title: 'polychild', comments: [2]}
      adapter.r['PUT:/comments/2'] = -> comments: {id: 2, title: 'original sibling', post: 1}
      adapter.r['POST:/comments'] = -> comments: {client_id: sibling.clientId, id: 3, message: 'sibling', post: 1}

      post = @Post.create(id: "1", title: 'parent');
      post.comments.addObject(@Comment.create(id: "2", message: 'child'))
      session.merge post

      comment = null
      sibling = null

      session.load(@Post, 1).then (post) ->
        comment = post.comments.firstObject
        sibling = session.create('comment', message: 'sibling')
        sibling.post = post
        comment.message = 'original sibling'
        post.title = 'polychild'
        expect(post.comments.toArray()).to.eql([comment, sibling])
        session.flush().then ->
          expect(post.comments.toArray()).to.eql([comment, sibling])
          expect(adapter.h).to.eql(['PUT:/posts/1', 'PUT:/comments/2', 'POST:/comments'])

    it 'updates with unloaded child', ->
      adapter.r['GET:/posts/1'] = -> posts: {id: 1, title: 'mvcc ftw', comments: [2]}
      adapter.r['PUT:/posts/1'] = -> posts: {id: 1, title: 'updated', comments: [2]}
      session.load('post', 1).then (post) ->
        expect(post.title).to.eq('mvcc ftw')
        expect(adapter.h).to.eql(['GET:/posts/1'])
        post.title = 'updated'
        session.flush().then ->
          expect(post.title).to.eq('updated')
          expect(adapter.h).to.eql(['GET:/posts/1', 'PUT:/posts/1'])

    it 'deletes child', ->
      adapter.r['PUT:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: [2]}
      adapter.r['DELETE:/comments/2'] = {}

      post = @Post.create(id: "1", title: 'parent');
      post.comments.addObject(@Comment.create(id: "2", message: 'child'))
      session.merge post

      session.load('post', 1).then (post) ->
        comment = post.comments.firstObject
        session.deleteModel(comment)
        expect(post.comments.length).to.eq(0)
        session.flush().then ->
          expect(adapter.h).to.eql(['DELETE:/comments/2'])
          expect(post.comments.length).to.eq(0)

    it 'deletes child and updates parent', ->
      adapter.r['PUT:/posts/1'] = posts: {id: 1, title: 'childless', comments: [2]}
      adapter.r['DELETE:/comments/2'] = {}

      post = @Post.create(id: "1", title: 'parent');
      post.comments.addObject(@Comment.create(id: "2", message: 'child'))
      session.merge post

      session.load('post', 1).then (post) ->
        comment = post.comments.firstObject
        session.deleteModel(comment)
        expect(post.comments.length).to.eq(0)
        post.title = 'childless'
        session.flush().then ->
          expect(adapter.h).to.eql(['PUT:/posts/1', 'DELETE:/comments/2'])
          expect(post.comments.length).to.eq(0)
          expect(post.title).to.eq('childless')

    it 'deletes parent and child', ->
      adapter.r['DELETE:/posts/1'] = {}
      adapter.r['DELETE:/comments/2'] = {}

      post = @Post.create(id: "1", title: 'parent');
      post.comments.addObject(@Comment.create(id: "2", message: 'child'))
      session.merge post

      session.load('post', 1).then (post) ->
        comment = post.comments.firstObject
        session.deleteModel(comment)
        expect(post.comments.length).to.eq(0)
        session.deleteModel(post)
        session.flush().then ->
          expect(adapter.h).to.eql(['DELETE:/posts/1', 'DELETE:/comments/2'])
          expect(post.isDeleted).to.be.true
          expect(comment.isDeleted).to.be.true

    context 'embedded', ->

      beforeEach ->
        PostSerializer = Ep.ModelSerializer.extend
          properties:
            comments:
              embedded: 'always'

        @container.register 'serializer:post', PostSerializer

      it 'loads', ->
        adapter.r['GET:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: [{id: 2, post: 1, message: 'first'}]}

        session.load(@Post, 1).then (post) ->
          expect(adapter.h).to.eql(['GET:/posts/1'])
          expect(post.id).to.eq("1")
          expect(post.title).to.eq('mvcc ftw')
          expect(post.comments.length).to.eq(1)
          comment = post.comments.firstObject
          expect(comment.message).to.eq 'first'
          expect(comment.post.isEqual(post)).to.be.true

      it 'updates child', ->
        adapter.r['GET:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: [{id: 2, post: 1, message: 'first'}]}
        adapter.r['PUT:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: [{id: 2, post: 1, message: 'first again'}]}

        session.load(@Post, 1).then (post) ->
          expect(adapter.h).to.eql(['GET:/posts/1'])
          comment = post.comments.firstObject
          comment.message = 'first again'
          session.flush().then ->
            expect(post.comments.firstObject).to.eq(comment)
            expect(comment.message).to.eq('first again')
            expect(adapter.h).to.eql(['GET:/posts/1', 'PUT:/posts/1'])

      it 'adds child', ->
        adapter.r['GET:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: []}
        adapter.r['PUT:/posts/1'] =  -> posts: {id: 1, title: 'mvcc ftw', comments: [{id: 2, client_id: comment.clientId, post: 1, message: 'reborn'}]}

        comment = null
        session.load(@Post, 1).then (post) ->
          expect(adapter.h).to.eql(['GET:/posts/1'])
          expect(post.comments.length).to.eq(0)
          comment = session.create('comment', message: 'reborn')
          comment.post = post
          session.flush().then ->
            expect(adapter.h).to.eql(['GET:/posts/1', 'PUT:/posts/1'])
            expect(comment.message).to.eq('reborn')
            expect(post.comments.firstObject).to.eq(comment)

      it 'deletes child', ->
        adapter.r['PUT:/posts/1'] = (url, type, hash) ->
          expect(hash.data.post.comments.length).to.eq(0)
          return posts: {id: 1, title: 'mvcc ftw', comments: []}

        post = @Post.create(id: "1", title: 'parent');
        post.comments.addObject(@Comment.create(id: "2", message: 'child'))
        session.merge post

        session.load('post', 1).then (post) ->
          comment = post.comments.firstObject
          session.deleteModel(comment)
          expect(post.comments.length).to.eq(0)
          session.flush().then ->
            expect(adapter.h).to.eql(['PUT:/posts/1'])
            expect(post.comments.length).to.eq(0)

      it 'deletes child with sibling', ->
        adapter.r['PUT:/posts/1'] = (url, type, hash) ->
          expect(hash.data.post.comments.length).to.eq(1)
          return posts: {id: 1, title: 'mvcc ftw', comments: [{id: 3, client_id: sibling.clientId, post: 1, message: 'child2'}]}

        post = @Post.create(id: "1", title: 'parent');
        post.comments.addObject(@Comment.create(id: "2", message: 'child1'))
        post.comments.addObject(@Comment.create(id: "3", message: 'child2'))
        session.merge post

        sibling = null
        session.load('post', 1).then (post) ->
          comment = post.comments.firstObject
          sibling = post.comments.lastObject
          session.deleteModel(comment)
          expect(post.comments.length).to.eq(1)
          session.flush().then ->
            expect(adapter.h).to.eql(['PUT:/posts/1'])
            expect(post.comments.length).to.eq(1)

      it 'new parent creates and deletes child before flush', ->
        adapter.r['POST:/posts'] = (url, type, hash) ->
          expect(hash.data.post.comments.length).to.eq(0)
          return posts: {client_id: post.clientId, id: 1, title: 'mvcc ftw', comments: []}

        post = session.create(@Post, title: 'parent')
        comment = session.create(@Comment, title: 'child')
        post.comments.pushObject comment
        post.comments.removeObject comment

        session.flush().then ->
          expect(post.comments.length).to.eq(0)
          expect(post.isNew).to.be.false
          expect(adapter.h).to.eql(['POST:/posts'])

      it 'deletes multiple children in multiple flushes', ->
        post = @Post.create(id: "1", title: 'parent');
        post.comments.addObject @Comment.create(id: "2", message: 'thing 1')
        post.comments.addObject @Comment.create(id: "3", message: 'thing 2')
        post = session.merge(post)

        adapter.r['PUT:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: [{post: "1", id: "3", message: 'thing 2'}]}

        session.deleteModel post.comments.objectAt(0)
        session.flush().then ->
          expect(adapter.h).to.eql(['PUT:/posts/1'])
          expect(post.comments.length).to.eq(1)
          session.deleteModel post.comments.objectAt(0)
          adapter.r['PUT:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: []}
          session.flush().then ->
            expect(adapter.h).to.eql(['PUT:/posts/1', 'PUT:/posts/1'])
            expect(post.comments.length).to.eq(0)

      it 'deletes parent and child', ->
        adapter.r['DELETE:/posts/1'] = {}

        post = @Post.create(id: "1", title: 'parent');
        post.comments.addObject(@Comment.create(id: "2", message: 'child'))
        session.merge post

        # TODO: once we have support for side deletions beef up this test
        session.load('post', 1).then (post) ->
          session.deleteModel(post)
          session.flush().then ->
            expect(adapter.h).to.eql(['DELETE:/posts/1'])
            expect(post.isDeleted).to.be.true

describe "rest", ->

  adapter = null
  session = null

  beforeEach ->
    require('./_shared').setupRest.apply(this)
    adapter = @adapter
    session = @session

  context 'one->many', ->

    beforeEach ->
      class @Post extends Ep.Model
        title: Ep.attr('string')
      @App.Post = @Post

      class @Comment extends Ep.Model
        message: Ep.attr('string')
        post: Ep.belongsTo(@Post)
      @App.Comment = @Comment

      @Post.reopen
        comments: Ep.hasMany(@Comment)

      @container.register 'model:post', @Post
      @container.register 'model:comment', @Comment

    it 'loads lazily', ->
      adapter.r['GET:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: [2]}
      adapter.r['GET:/comments/2'] = comments: {id: 2, message: 'first', post: 1}

      session.load('post', 1).then (post) ->
        expect(adapter.h).to.eql(['GET:/posts/1'])
        expect(post.id).to.eq("1")
        expect(post.title).to.eq('mvcc ftw')
        expect(post.comments.length).to.eq(1)
        comment = post.comments.firstObject
        expect(comment.message).to.be.undefined

        post.comments.firstObject.then ->
          expect(adapter.h).to.eql(['GET:/posts/1', 'GET:/comments/2'])
          expect(comment.message).to.eq('first')
          expect(comment.post.isEqual(post)).to.be.true

    it 'creates', ->
      adapter.r['POST:/posts'] = -> posts: {client_id: post.clientId, id: 1, title: 'topological sort', comments: []}
      adapter.r['POST:/comments'] = (url, type, hash) ->
        expect(hash.data.comment.post).to.eq(1)
        return comments: {client_id: comment.clientId, id: 2, message: 'seems good', post: 1}

      post = session.create('post')
      post.title = 'topological sort'

      comment = session.create('comment')
      comment.message = 'seems good'
      comment.post = post

      expect(post.comments.firstObject).to.eq(comment)

      session.flush().then ->
        expect(post.id).to.not.be.null
        expect(post.isNew).to.be.false
        expect(post.title).to.eq('topological sort')
        expect(comment.id).to.not.be.null
        expect(comment.message).to.eq('seems good')
        expect(comment.post).to.eq(post)
        expect(comment.post.id).to.eq("1")
        expect(post.comments.firstObject).to.eq(comment)
        expect(adapter.h).to.eql(['POST:/posts', 'POST:/comments'])

    it 'creates child', ->
      adapter.r['POST:/comments'] = -> comments: {client_id: comment.clientId, id: 2, message: 'new child', post: 1}

      session.merge @Post.create(id: "1", title: 'parent');

      comment = null

      session.load(@Post, 1).then (post) ->
        comment = session.create('comment', message: 'new child')
        comment.post = post
        expect(post.comments.toArray()).to.eql([comment])
        session.flush().then ->
          expect(post.comments.toArray()).to.eql([comment])
          expect(comment.message).to.eq('new child')
          expect(adapter.h).to.eql(['POST:/comments'])

    it 'create followed by delete does not hit server', ->
      session.merge @Post.create(id: "1", title: 'parent');

      comment = null

      session.load(@Post, 1).then (post) ->
        comment = session.create('comment', message: 'new child')
        comment.post = post
        session.deleteModel comment
        session.flush().then ->
          expect(adapter.h).to.eql([])
          expect(comment.isDeleted).to.be.true

    it 'updates parent, updates child, and saves sibling', ->
      adapter.r['PUT:/posts/1'] = -> post: {id: 1, title: 'polychild', comments: [2]}
      adapter.r['PUT:/comments/2'] = -> comments: {id: 2, title: 'original sibling', post: 1}
      adapter.r['POST:/comments'] = -> comments: {client_id: sibling.clientId, id: 3, message: 'sibling', post: 1}

      post = @Post.create(id: "1", title: 'parent');
      post.comments.addObject(@Comment.create(id: "2", message: 'child'))
      session.merge post

      comment = null
      sibling = null

      session.load(@Post, 1).then (post) ->
        comment = post.comments.firstObject
        sibling = session.create('comment', message: 'sibling')
        sibling.post = post
        comment.message = 'original sibling'
        post.title = 'polychild'
        expect(post.comments.toArray()).to.eql([comment, sibling])
        session.flush().then ->
          expect(post.comments.toArray()).to.eql([comment, sibling])
          expect(adapter.h).to.eql(['PUT:/posts/1', 'PUT:/comments/2', 'POST:/comments'])

    it 'updates with unloaded child', ->
      adapter.r['GET:/posts/1'] = -> posts: {id: 1, title: 'mvcc ftw', comments: [2]}
      adapter.r['PUT:/posts/1'] = -> posts: {id: 1, title: 'updated', comments: [2]}
      session.load('post', 1).then (post) ->
        expect(post.title).to.eq('mvcc ftw')
        expect(adapter.h).to.eql(['GET:/posts/1'])
        post.title = 'updated'
        session.flush().then ->
          expect(post.title).to.eq('updated')
          expect(adapter.h).to.eql(['GET:/posts/1', 'PUT:/posts/1'])

    it 'deletes child', ->
      adapter.r['PUT:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: [2]}
      adapter.r['DELETE:/comments/2'] = {}

      post = @Post.create(id: "1", title: 'parent');
      post.comments.addObject(@Comment.create(id: "2", message: 'child'))
      session.merge post

      session.load('post', 1).then (post) ->
        comment = post.comments.firstObject
        session.deleteModel(comment)
        expect(post.comments.length).to.eq(0)
        session.flush().then ->
          expect(adapter.h).to.eql(['DELETE:/comments/2'])
          expect(post.comments.length).to.eq(0)

    it 'deletes child and updates parent', ->
      adapter.r['PUT:/posts/1'] = posts: {id: 1, title: 'childless', comments: [2]}
      adapter.r['DELETE:/comments/2'] = {}

      post = @Post.create(id: "1", title: 'parent');
      post.comments.addObject(@Comment.create(id: "2", message: 'child'))
      session.merge post

      session.load('post', 1).then (post) ->
        comment = post.comments.firstObject
        session.deleteModel(comment)
        expect(post.comments.length).to.eq(0)
        post.title = 'childless'
        session.flush().then ->
          expect(adapter.h).to.eql(['PUT:/posts/1', 'DELETE:/comments/2'])
          expect(post.comments.length).to.eq(0)
          expect(post.title).to.eq('childless')

    it 'deletes parent and child', ->
      adapter.r['DELETE:/posts/1'] = {}
      adapter.r['DELETE:/comments/2'] = {}

      post = @Post.create(id: "1", title: 'parent');
      post.comments.addObject(@Comment.create(id: "2", message: 'child'))
      session.merge post

      session.load('post', 1).then (post) ->
        comment = post.comments.firstObject
        session.deleteModel(comment)
        expect(post.comments.length).to.eq(0)
        session.deleteModel(post)
        session.flush().then ->
          expect(adapter.h).to.eql(['DELETE:/posts/1', 'DELETE:/comments/2'])
          expect(post.isDeleted).to.be.true
          expect(comment.isDeleted).to.be.true

    context 'embedded', ->

      beforeEach ->
        PostSerializer = Ep.ModelSerializer.extend
          properties:
            comments:
              embedded: 'always'

        @container.register 'serializer:post', PostSerializer

      it 'loads', ->
        adapter.r['GET:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: [{id: 2, post: 1, message: 'first'}]}

        session.load(@Post, 1).then (post) ->
          expect(adapter.h).to.eql(['GET:/posts/1'])
          expect(post.id).to.eq("1")
          expect(post.title).to.eq('mvcc ftw')
          expect(post.comments.length).to.eq(1)
          comment = post.comments.firstObject
          expect(comment.message).to.eq 'first'
          expect(comment.post.isEqual(post)).to.be.true

      it 'updates child', ->
        adapter.r['GET:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: [{id: 2, post: 1, message: 'first'}]}
        adapter.r['PUT:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: [{id: 2, post: 1, message: 'first again'}]}

        session.load(@Post, 1).then (post) ->
          expect(adapter.h).to.eql(['GET:/posts/1'])
          comment = post.comments.firstObject
          comment.message = 'first again'
          session.flush().then ->
            expect(post.comments.firstObject).to.eq(comment)
            expect(comment.message).to.eq('first again')
            expect(adapter.h).to.eql(['GET:/posts/1', 'PUT:/posts/1'])

      it 'adds child', ->
        adapter.r['GET:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: []}
        adapter.r['PUT:/posts/1'] =  -> posts: {id: 1, title: 'mvcc ftw', comments: [{id: 2, client_id: comment.clientId, post: 1, message: 'reborn'}]}

        comment = null
        session.load(@Post, 1).then (post) ->
          expect(adapter.h).to.eql(['GET:/posts/1'])
          expect(post.comments.length).to.eq(0)
          comment = session.create('comment', message: 'reborn')
          comment.post = post
          session.flush().then ->
            expect(adapter.h).to.eql(['GET:/posts/1', 'PUT:/posts/1'])
            expect(comment.message).to.eq('reborn')
            expect(post.comments.firstObject).to.eq(comment)

      it 'deletes child', ->
        adapter.r['PUT:/posts/1'] = (url, type, hash) ->
          expect(hash.data.post.comments.length).to.eq(0)
          return posts: {id: 1, title: 'mvcc ftw', comments: []}

        post = @Post.create(id: "1", title: 'parent');
        post.comments.addObject(@Comment.create(id: "2", message: 'child'))
        session.merge post

        # HACK: required because all knowledge of embeddedness is tracked by the adapter
        adapter._embeddedManager.updateParents(post);

        session.load('post', 1).then (post) ->
          comment = post.comments.firstObject
          session.deleteModel(comment)
          expect(post.comments.length).to.eq(0)
          session.flush().then ->
            expect(adapter.h).to.eql(['PUT:/posts/1'])
            expect(post.comments.length).to.eq(0)

      it 'deletes child with sibling', ->
        adapter.r['PUT:/posts/1'] = (url, type, hash) ->
          expect(hash.data.post.comments.length).to.eq(1)
          return posts: {id: 1, title: 'mvcc ftw', comments: [{id: 3, client_id: sibling.clientId, post: 1, message: 'child2'}]}

        post = @Post.create(id: "1", title: 'parent');
        post.comments.addObject(@Comment.create(id: "2", message: 'child1'))
        post.comments.addObject(@Comment.create(id: "3", message: 'child2'))
        session.merge post

        # HACK: required because all knowledge of embeddedness is tracked by the adapter
        adapter._embeddedManager.updateParents(post);

        sibling = null
        session.load('post', 1).then (post) ->
          comment = post.comments.firstObject
          sibling = post.comments.lastObject
          session.deleteModel(comment)
          expect(post.comments.length).to.eq(1)
          session.flush().then ->
            expect(adapter.h).to.eql(['PUT:/posts/1'])
            expect(post.comments.length).to.eq(1)

      it 'new parent creates and deletes child before flush', ->
        adapter.r['POST:/posts'] = (url, type, hash) ->
          expect(hash.data.post.comments.length).to.eq(0)
          return posts: {client_id: post.clientId, id: 1, title: 'mvcc ftw', comments: []}

        post = session.create(@Post, title: 'parent')
        comment = session.create(@Comment, title: 'child')
        post.comments.pushObject comment
        post.comments.removeObject comment

        session.flush().then ->
          expect(post.comments.length).to.eq(0)
          expect(post.isNew).to.be.false
          expect(adapter.h).to.eql(['POST:/posts'])

      it 'deletes multiple children in multiple flushes', ->
        post = @Post.create(id: "1", title: 'parent');
        post.comments.addObject @Comment.create(id: "2", message: 'thing 1')
        post.comments.addObject @Comment.create(id: "3", message: 'thing 2')
        post = session.merge(post)

        # HACK: required because all knowledge of embeddedness is tracked by the adapter
        adapter._embeddedManager.updateParents(post);

        adapter.r['PUT:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: [{post: "1", id: "3", message: 'thing 2'}]}

        session.deleteModel post.comments.objectAt(0)
        session.flush().then ->
          expect(adapter.h).to.eql(['PUT:/posts/1'])
          expect(post.comments.length).to.eq(1)
          session.deleteModel post.comments.objectAt(0)
          adapter.r['PUT:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: []}
          session.flush().then ->
            expect(adapter.h).to.eql(['PUT:/posts/1', 'PUT:/posts/1'])
            expect(post.comments.length).to.eq(0)

      it 'deletes parent and child', ->
        adapter.r['DELETE:/posts/1'] = {}

        post = @Post.create(id: "1", title: 'parent');
        post.comments.addObject(@Comment.create(id: "2", message: 'child'))
        session.merge post

        # HACK: required because all knowledge of embeddedness is tracked by the adapter
        adapter._embeddedManager.updateParents(post);

        # TODO: once we have support for side deletions beef up this test
        session.load('post', 1).then (post) ->
          session.deleteModel(post)
          session.flush().then ->
            expect(adapter.h).to.eql(['DELETE:/posts/1'])
            expect(post.isDeleted).to.be.true


Remove button padding

Remove button padding

# This gtkrc makes the assumption that:
#  2.25pt = 3px
#  3.5pt  = 4px

style "default"
{
    # Will be overriden in some widgets
    color["focus_line"] = "#FFFFFF"

    bg[NORMAL] = "#808080"
    bg[PRELIGHT] = "#808080"
    bg[ACTIVE] = "#ffffff"
    bg[SELECTED] = "#e7e7e7"

    fg[NORMAL]        = "#000000"
    fg[ACTIVE]        = "#000000"
    fg[SELECTED]      = "#ffffff"
    fg[PRELIGHT]      = "#ffffff"

    text[NORMAL] = "#000000" # "#7f7f7f" 1. need to patch GTK+ to use ACTIVE when focused, 2. what color should this really be?
    text[ACTIVE] = "#000000"
    text[SELECTED] = "#000000"
    text[PRELIGHT] = "#000000"

    base[NORMAL] = "#E6E6E6"
    base[ACTIVE] = "#ffffff"
    base[SELECTED] = "#a6a6a6"
    base[PRELIGHT] = "#ffffff"

    fg[INSENSITIVE] = "#808080"
    text[INSENSITIVE] = "#808080"

    xthickness = 3   # line_width
    ythickness = 3

    # A lot of these will probably need to be changed, but this has to
    # be done when the exact sizes are known
    GtkWidget::interior-focus = 0
    GtkWidget::focus-line-width = 1   # This does not do anything really
    GtkWidget::focus-padding = 0

    GtkWidget::wide-separators = 1
    GtkWidget::separator-height = 3 #line_width
    GtkWidget::separator-width = 3

    GtkRange::activate-slider = 1
    GtkButton::inner-border = { 6, 6, 6, 6 }           # ??

    GtkButton::default-border = { 1, 1, 1, 1 }         # line_width - focus-line-width
    GtkButton::default-outside-border = { 2, 2, 2, 2 } # focus-line-width

    GtkScrolledWindow::scrollbar-spacing = 0

    GtkCheckMenuItem::indicator-size = 20 # how to calculate this? and is it OK like this?
    GtkExpander::expander-size = 20       # same as indicator-size
    GtkExpander::expander-spacing = 2     # XXX

    GtkTreeView::expander-size = 20       # same as indicator-size

    engine "sugar" {
        # Is this correct? Should we make sure it is pixel aligned?
        # 6 pixel for the XO version, and 3 for the "normal" one
        line_width = 3.0
        # 9-10 pixel for the XO version, and 4 for the "normal" one
        thick_line_width = 4.0

        max_radius = 20.0
    }
}

style "window-content"
{
    color["bg_color"] = "#C0C0C0"

    bg[INSENSITIVE] = @bg_color
    base[INSENSITIVE] = @bg_color
}

style "window"
{
    color["bg_color"] = "#C0C0C0"

    bg[NORMAL] = @bg_color
}

style "scrollbar"
{
    GtkRange::slider-width = 10    # ?
    GtkRange::trough-border = 3    # line_width
    GtkRange::stepper-spacing = 0
    GtkScrollbar::min-slider-length = 30    # ?
    GtkScrollbar::has-forward-stepper = 0
    GtkScrollbar::has-backward-stepper = 0
    GtkScrollbar::has-secondary-forward-stepper = 0
    GtkScrollbar::has-secondary-backward-stepper = 0

    # Hack for mozilla as it does not obey the above style properties ...
    # Really, mozilla should be fixed ...
    GtkRange::stepper-size = 1

    engine "sugar" {
        hint = "scrollbar"
    }
}

style "scale"
{
    GtkWidget::focus-line-width = 0

    # "square" slider (really round of course)
    # Same as indicator-size?
    GtkRange::slider-width = 20
    GtkScale::slider-length = 20
    GtkRange::trough-border = 0
    GtkRange::stepper-spacing = 0
    GtkRange::trough-side-details = 1
}

# hints for the trough sizing hacks
style "hscale" = "scale"
{
    engine "sugar" {
        hint = "hscale"
    }
}

style "vscale" = "scale"
{
    engine "sugar" {
        hint = "vscale"
    }
}

style "spinbutton"
{
    engine "sugar" {
        hint = "spinbutton"
    }
}

style "comboboxentry"
{
    engine "sugar" {
        hint = "comboboxentry"
    }
}

style "frame"
{
    bg[NORMAL] = "#404040"
}

style "notebook"
{
    GtkNotebook::tab-overlap = -2

    xthickness = 0
    ythickness = 0
}

style "toolbutton"
{
    color["focus_line"] = "#000000"

    # This makes the normal background of comboboxes invisible
    bg[NORMAL] = "#404040"
    bg[PRELIGHT] = "#000000"

    fg[NORMAL] = "#FFFFFF"
    fg[ACTIVE] = "#FFFFFF"
    fg[PRELIGHT] = "#ffffff"

    # Work around GTK+ bug #382646
    text[NORMAL] = "#FFFFFF"
    text[ACTIVE] = "#FFFFFF"
    text[PRELIGHT] = "#ffffff"

    engine "sugar" {
        max_radius = 5.0
    }
}

style "toolbox"
{
    GtkToolbar::shadow-type = GTK_SHADOW_NONE

    color["bg_color"] = "#404040"

    bg[ACTIVE] = "#808080"
    bg[NORMAL] = @bg_color
    bg[INSENSITIVE] = @bg_color

    fg[NORMAL] = "#FFFFFF"
    fg[ACTIVE] = "#FFFFFF"
}

style "panel"
{
    bg[NORMAL] = "#C0C0C0"
    fg[NORMAL] = "#000000"
}

style "entry"
{
    # small inner border and a large x/ythickness for entries
    # to reduce the number of hacks needed :-)
    # This size needs to be choosen so the round corner work with the height
    # Something like (height / 2.0 - line_width) / sqrt(2), where height
    # is font height + x/ythickness
    xthickness = 7
    ythickness = 7

    GtkEntry::inner-border = { 0, 0, 0, 0 }

    GtkWidget::focus-line-width = 0
}

style "button"
{
    fg[NORMAL]        = "#ffffff"
}

style "checkbutton"
{
    fg[NORMAL]        = "#000000"
    fg[ACTIVE]        = "#000000"

    # I won't get the exact sizing, but this should be pretty close
    GtkCheckButton::indicator-size = 13
    GtkCheckButton::indicator-spacing = 3
    GtkWidget::focus-padding = 3
}

style "progressbar"
{
    xthickness = 0
    ythickness = 0

    # Make the bar visible (only until the engine implements it!)
    bg[PRELIGHT] = "#ffffff"
}

class "GtkWidget"      style "default"
class "GtkWindow"      style "window"
class "GtkEventBox"    style "window"
class "GtkNotebook"    style "notebook"
class "GtkEntry"       style "entry"
class "GtkSpinButton"  style "spinbutton"
class "GtkScrollbar"   style "scrollbar"
class "GtkHScale"      style "hscale"
class "GtkVScale"      style "vscale"
class "GtkProgressBar" style "progressbar"

widget_class "<GtkWindow>.*"      style "window-content"
widget_class "*<GtkComboBoxEntry>*" style "comboboxentry"
widget_class "*<GtkCombo>*" style "comboboxentry"
widget_class "*<SugarToolbox>*" style "toolbox"
widget_class "*<GtkToolButton>*" style "toolbutton"
widget_class "*<GtkToolItem>*<GtkComboBox>*" style "toolbutton"
widget_class "*<GtkToolItem>*<GtkButton>*" style "toolbutton"

widget_class "*<GtkButton>*" style "button"
widget_class "*<GtkCheckButton>*" style "checkbutton"

widget_class "*<SugarPanel>" style "panel"

widget_class "*<SugarFrameWindow>*" style "frame"

# This gtkrc makes the assumption that:
#  2.25pt = 3px
#  3.5pt  = 4px

style "default"
{
    # Will be overriden in some widgets
    color["focus_line"] = "#FFFFFF"

    bg[NORMAL] = "#808080"
    bg[PRELIGHT] = "#808080"
    bg[ACTIVE] = "#ffffff"
    bg[SELECTED] = "#e7e7e7"

    fg[NORMAL]        = "#000000"
    fg[ACTIVE]        = "#000000"
    fg[SELECTED]      = "#ffffff"
    fg[PRELIGHT]      = "#ffffff"

    text[NORMAL] = "#000000" # "#7f7f7f" 1. need to patch GTK+ to use ACTIVE when focused, 2. what color should this really be?
    text[ACTIVE] = "#000000"
    text[SELECTED] = "#000000"
    text[PRELIGHT] = "#000000"

    base[NORMAL] = "#E6E6E6"
    base[ACTIVE] = "#ffffff"
    base[SELECTED] = "#a6a6a6"
    base[PRELIGHT] = "#ffffff"

    fg[INSENSITIVE] = "#808080"
    text[INSENSITIVE] = "#808080"

    xthickness = 3   # line_width
    ythickness = 3

    # A lot of these will probably need to be changed, but this has to
    # be done when the exact sizes are known
    GtkWidget::interior-focus = 0
    GtkWidget::focus-line-width = 1   # This does not do anything really
    GtkWidget::focus-padding = 0

    GtkWidget::wide-separators = 1
    GtkWidget::separator-height = 3 #line_width
    GtkWidget::separator-width = 3

    GtkRange::activate-slider = 1
    GtkButton::inner-border = { 6, 6, 6, 6 }           # ??

    GtkButton::default-border = { 1, 1, 1, 1 }         # line_width - focus-line-width
    GtkButton::default-outside-border = { 2, 2, 2, 2 } # focus-line-width

    GtkScrolledWindow::scrollbar-spacing = 0

    GtkCheckMenuItem::indicator-size = 20 # how to calculate this? and is it OK like this?
    GtkExpander::expander-size = 20       # same as indicator-size
    GtkExpander::expander-spacing = 2     # XXX

    GtkTreeView::expander-size = 20       # same as indicator-size

    engine "sugar" {
        # Is this correct? Should we make sure it is pixel aligned?
        # 6 pixel for the XO version, and 3 for the "normal" one
        line_width = 3.0
        # 9-10 pixel for the XO version, and 4 for the "normal" one
        thick_line_width = 4.0

        max_radius = 20.0
    }
}

style "window-content"
{
    color["bg_color"] = "#C0C0C0"

    bg[INSENSITIVE] = @bg_color
    base[INSENSITIVE] = @bg_color
}

style "window"
{
    color["bg_color"] = "#C0C0C0"

    bg[NORMAL] = @bg_color
}

style "scrollbar"
{
    GtkRange::slider-width = 10    # ?
    GtkRange::trough-border = 3    # line_width
    GtkRange::stepper-spacing = 0
    GtkScrollbar::min-slider-length = 30    # ?
    GtkScrollbar::has-forward-stepper = 0
    GtkScrollbar::has-backward-stepper = 0
    GtkScrollbar::has-secondary-forward-stepper = 0
    GtkScrollbar::has-secondary-backward-stepper = 0

    # Hack for mozilla as it does not obey the above style properties ...
    # Really, mozilla should be fixed ...
    GtkRange::stepper-size = 1

    engine "sugar" {
        hint = "scrollbar"
    }
}

style "scale"
{
    GtkWidget::focus-line-width = 0

    # "square" slider (really round of course)
    # Same as indicator-size?
    GtkRange::slider-width = 20
    GtkScale::slider-length = 20
    GtkRange::trough-border = 0
    GtkRange::stepper-spacing = 0
    GtkRange::trough-side-details = 1
}

# hints for the trough sizing hacks
style "hscale" = "scale"
{
    engine "sugar" {
        hint = "hscale"
    }
}

style "vscale" = "scale"
{
    engine "sugar" {
        hint = "vscale"
    }
}

style "spinbutton"
{
    engine "sugar" {
        hint = "spinbutton"
    }
}

style "comboboxentry"
{
    engine "sugar" {
        hint = "comboboxentry"
    }
}

style "frame"
{
    bg[NORMAL] = "#404040"
}

style "notebook"
{
    GtkNotebook::tab-overlap = -2

    xthickness = 0
    ythickness = 0
}

style "toolbutton"
{
    color["focus_line"] = "#000000"

    # This makes the normal background of comboboxes invisible
    bg[NORMAL] = "#404040"
    bg[PRELIGHT] = "#000000"

    fg[NORMAL] = "#FFFFFF"
    fg[ACTIVE] = "#FFFFFF"
    fg[PRELIGHT] = "#ffffff"

    # Work around GTK+ bug #382646
    text[NORMAL] = "#FFFFFF"
    text[ACTIVE] = "#FFFFFF"
    text[PRELIGHT] = "#ffffff"

    GtkButton::inner-border = { 6, 6, 6, 6 }

    engine "sugar" {
        max_radius = 5.0
    }
}

style "toolbox"
{
    GtkToolbar::shadow-type = GTK_SHADOW_NONE

    color["bg_color"] = "#404040"

    bg[ACTIVE] = "#808080"
    bg[NORMAL] = @bg_color
    bg[INSENSITIVE] = @bg_color

    fg[NORMAL] = "#FFFFFF"
    fg[ACTIVE] = "#FFFFFF"
}

style "panel"
{
    bg[NORMAL] = "#C0C0C0"
    fg[NORMAL] = "#000000"
}

style "entry"
{
    # small inner border and a large x/ythickness for entries
    # to reduce the number of hacks needed :-)
    # This size needs to be choosen so the round corner work with the height
    # Something like (height / 2.0 - line_width) / sqrt(2), where height
    # is font height + x/ythickness
    xthickness = 7
    ythickness = 7

    GtkEntry::inner-border = { 0, 0, 0, 0 }

    GtkWidget::focus-line-width = 0
}

style "button"
{
    fg[NORMAL]        = "#ffffff"
}

style "checkbutton"
{
    fg[NORMAL]        = "#000000"
    fg[ACTIVE]        = "#000000"

    # I won't get the exact sizing, but this should be pretty close
    GtkCheckButton::indicator-size = 13
    GtkCheckButton::indicator-spacing = 3
    GtkWidget::focus-padding = 3
}

style "progressbar"
{
    xthickness = 0
    ythickness = 0

    # Make the bar visible (only until the engine implements it!)
    bg[PRELIGHT] = "#ffffff"
}

class "GtkWidget"      style "default"
class "GtkWindow"      style "window"
class "GtkEventBox"    style "window"
class "GtkNotebook"    style "notebook"
class "GtkEntry"       style "entry"
class "GtkSpinButton"  style "spinbutton"
class "GtkScrollbar"   style "scrollbar"
class "GtkHScale"      style "hscale"
class "GtkVScale"      style "vscale"
class "GtkProgressBar" style "progressbar"

widget_class "<GtkWindow>.*"      style "window-content"
widget_class "*<GtkComboBoxEntry>*" style "comboboxentry"
widget_class "*<GtkCombo>*" style "comboboxentry"
widget_class "*<SugarToolbox>*" style "toolbox"
widget_class "*<GtkToolButton>*" style "toolbutton"
widget_class "*<GtkToolItem>*<GtkComboBox>*" style "toolbutton"
widget_class "*<GtkToolItem>*<GtkButton>*" style "toolbutton"

widget_class "*<GtkButton>*" style "button"
widget_class "*<GtkCheckButton>*" style "checkbutton"

widget_class "*<SugarPanel>" style "panel"

widget_class "*<SugarFrameWindow>*" style "frame"


Fixed small fonts on simpleInput.em by linking to prompt.css.

if (typeof(std.core.SimpleInput) != 'undefined')
    throw new Error('Error in std.core.SimpleInput.  May only import once.');

(function ()
 {
     var knownUsed = [];

     var SIMPLE_INPUT_MOD_NAME = 'SimpleInput';
     var haveInited = false;
     var guiMod = simulator._simulator.addGUITextModule(
         SIMPLE_INPUT_MOD_NAME,
         getSimpleInputGUIText(),
         function()
         {
             haveInited = true;
             guiMod.bind('responseReceived',
                         respReceivedFunc);
         }
     );

     function respReceivedFunc(forWhom,response)
     {
         if ((!(forWhom in knownUsed)) ||
             (knownUsed[forWhom] == null))
         {
             throw new Error('\nWarning in simple input: ' +
                             'recevied response for '      +
                             forWhom.toString() + ' when ' +
                             'have no record for it.\n');

         }

         var inputter = knownUsed[forWhom];
         knownUsed[forWhom] = null;
         inputter.cb(response);
     }

     /**
      @param {int} type what type of simple input going to provide
      (See std.core.SimpleInput.SELECT_LIST and
      std.core.SimpleInput.ENTER_TEXT as examples

      @param {string} message what message gets written at the top of
      the input.

      @param {function} cb Takes a single argument -- the value of
      what was selected.

      @param additional (optional)

        if ENTER_TEXT is selected, then this should be undefined.

        if SELECT_LIST is entered as type, then this should be an
        arrray of tuples.  The first element of each tuple should
        contain the text that gets displayed to the user next to the
        option.  The second element should contain a code that gets
        passed back to cb so that the scripter knows what was
        selected.  Code must be text.
      */
     std.core.SimpleInput = function(type, message, cb, additional)
     {
         if (!haveInited)
             throw new Error('Error.  GUI still initializing.  Please wait.');

         this.type       = type;
         this.message    = message;
         this.cb         = cb;
         this.additional = additional;

         if ((type == std.core.SimpleInput.SELECT_LIST) &&
             (typeof(this.additional) == 'undefined'))
         {
             throw new Error ('Error constructing SimpleInput.  If given ' +
                              'a list input, should be given additional '  +
                              'labels.');
         }

         this.indexUsed = addToKnownUsed(this);

         var callFunc = '';
         if (type == std.core.SimpleInput.SELECT_LIST)
             callFunc = 'simpleInputSelectList';
         else if (type == std.core.SimpleInput.ENTER_TEXT)
             callFunc = 'simpleInputSelectText';
         else
             throw new Error ('Error: please select a valid type ' +
                              'for SimpleInput');

         guiMod.call(callFunc,message,this.indexUsed,additional);
     };

     /*** Types of simple input to get  ***/
     //note: if add to any additional ones of these, then
     //must also add to if-else if statement in constructor
     //of SimpleInput
     std.core.SimpleInput.SELECT_LIST = 0;
     std.core.SimpleInput.ENTER_TEXT  = 1;

     std.core.SimpleInput.ready = function()
     {
         return haveInited;
     };

     /**
      We want to recycle the divs,etc. associ
      */
     function addToKnownUsed(simpleInputObject)
     {
         var indexToUse = null;
         for (var s in knownUsed)
         {
             if (knownUsed[s] == null)
             {
                 indexToUse = s;
                 break;
             }
         }

         //could not find empty index.  Create new element
         if (indexToUse == null)
         {
             knownUsed.push(null);
             indexToUse = knownUsed.length - 1;
         }
         knownUsed[indexToUse] = simpleInputObject;
         return indexToUse;
     }

     function getSimpleInputGUIText()
     {
         var returner = "sirikata.ui('" + SIMPLE_INPUT_MOD_NAME + "',";
         returner += 'function(){ ';

         returner += @

         var newcsslink =
             $("<link />").attr({rel:'stylesheet', type:'text/css', href:'../scripting/prompt.css'});
	 $("head").append(newcsslink);

         //keeps track of all open windows requiring input events.
         var existingWindows = { };

         function generateDivIDFromInputID(inputID)
         {
             return 'simpleInputID__' + inputID.toString();
         }

         function newWindow(htmlForWindow,simpleInputID)
         {
             var newWindowID = generateDivIDFromInputID(simpleInputID);
             $('
'   +
               htmlForWindow   +
               '
' //end div at top.
              ).attr({id:newWindowID,title:'simpleInput'}).appendTo('body');

             var inputWindow = new sirikata.ui.window(
                 '#' + newWindowID,
                 {
	             autoOpen: true,
	             height: 'auto',
	             width: 300,
                     height: 300,
                     position: 'right'
                 }
             );
             inputWindow.show();
             existingWindows[newWindowID] = [inputWindow,simpleInputID];
         }

         simpleInputSubmit = function(simpleInputID)
         {
             var returner = null;
             //first check to see if we got the value for a text element
             var textInputID = generateSimpleInputOptionID(simpleInputID);
             var textInput = $('#' + textInputID);
             if (textInput.length > 0)
             {
                 //means that we got a text input
                 returner =textInput.val();
             }
             else
             {
                 var radioInput = $('input:radio[name=' +
                                    textInputID +']:checked');
                 if (radioInput.length > 0)
                     returner = radioInput.val();
             }

             if (returner === null)
             {
                 sirikata.log('error','In simpleInput, selecting ' +
                              'an input that does not exist.');
             }
             else
             {
                 sirikata.event('responseReceived',simpleInputID,returner);
             }

             delete existingWindows[simpleInputID];
             //actually remove the window from view after its question has been answered.
             var inputID = generateDivIDFromInputID(simpleInputID);
             $('#'+inputID).remove();
         };

         function generateSubmitButtonHtml(simpleInputID)
         {
             var htmlToDisplay = '';
             htmlToDisplay += '<button ' +
                 'onclick="simpleInputSubmit(' + simpleInputID.toString() +
                 ')">';

             htmlToDisplay += 'Submit';
             htmlToDisplay += '</button>';

             return htmlToDisplay;
         }

         simpleInputSelectList =
             function(message,simpleInputID,additional)
         {
             var htmlInsert = message + '
';
             var radName = generateSimpleInputOptionID(simpleInputID);
             for (var s in additional)
             {
                 htmlInsert += '<input type=radio name="' +
                     radName + '" value="' + additional[s][1] +
                     '"></input>' + additional[s][0];
                 htmlInsert += '
';
             }
             htmlInsert += generateSubmitButtonHtml(simpleInputID);
             newWindow(htmlInsert,simpleInputID);
         };

         function generateSimpleInputOptionID(inputID)
         {
             return 'simpleInputIDOption__' + inputID.toString();
         }

         simpleInputSelectText = function(message,simpleInputID)
         {
             var htmlInsert = message + '
';
             htmlInsert += '<input id="'                      +
                 generateSimpleInputOptionID(simpleInputID)   +
                 '"></input>
';
             htmlInsert += generateSubmitButtonHtml(simpleInputID);
             newWindow(htmlInsert,simpleInputID);

             //handles enter submitting text
             var handleEnterHit = function(evt)
             {
                 //13 represents keycode for enter, submits whatever's in
                 //the text box if user hits enter.
                 if (evt.keyCode == 13)
                     simpleInputSubmit(simpleInputID);
             };

             //copied from chat.js
             var register_area = document.getElementById(
                 generateSimpleInputOptionID(simpleInputID));
             register_area.onkeyup = handleEnterHit;
         };
         @;

         returner += '});';
         return returner;
     }

 }
)();

if (typeof(std.core.SimpleInput) != 'undefined')
    throw new Error('Error in std.core.SimpleInput.  May only import once.');

(function ()
 {
     var knownUsed = [];

     var SIMPLE_INPUT_MOD_NAME = 'SimpleInput';
     var haveInited = false;
     var guiMod = simulator._simulator.addGUITextModule(
         SIMPLE_INPUT_MOD_NAME,
         getSimpleInputGUIText(),
         function()
         {
             haveInited = true;
             guiMod.bind('responseReceived',
                         respReceivedFunc);
         }
     );

     function respReceivedFunc(forWhom,response)
     {
         if ((!(forWhom in knownUsed)) ||
             (knownUsed[forWhom] == null))
         {
             throw new Error('\nWarning in simple input: ' +
                             'recevied response for '      +
                             forWhom.toString() + ' when ' +
                             'have no record for it.\n');

         }

         var inputter = knownUsed[forWhom];
         knownUsed[forWhom] = null;
         inputter.cb(response);
     }

     /**
      @param {int} type what type of simple input going to provide
      (See std.core.SimpleInput.SELECT_LIST and
      std.core.SimpleInput.ENTER_TEXT as examples

      @param {string} message what message gets written at the top of
      the input.

      @param {function} cb Takes a single argument -- the value of
      what was selected.

      @param additional (optional)

        if ENTER_TEXT is selected, then this should be undefined.

        if SELECT_LIST is entered as type, then this should be an
        arrray of tuples.  The first element of each tuple should
        contain the text that gets displayed to the user next to the
        option.  The second element should contain a code that gets
        passed back to cb so that the scripter knows what was
        selected.  Code must be text.
      */
     std.core.SimpleInput = function(type, message, cb, additional)
     {
         if (!haveInited)
             throw new Error('Error.  GUI still initializing.  Please wait.');

         this.type       = type;
         this.message    = message;
         this.cb         = cb;
         this.additional = additional;

         if ((type == std.core.SimpleInput.SELECT_LIST) &&
             (typeof(this.additional) == 'undefined'))
         {
             throw new Error ('Error constructing SimpleInput.  If given ' +
                              'a list input, should be given additional '  +
                              'labels.');
         }

         this.indexUsed = addToKnownUsed(this);

         var callFunc = '';
         if (type == std.core.SimpleInput.SELECT_LIST)
             callFunc = 'simpleInputSelectList';
         else if (type == std.core.SimpleInput.ENTER_TEXT)
             callFunc = 'simpleInputSelectText';
         else
             throw new Error ('Error: please select a valid type ' +
                              'for SimpleInput');

         guiMod.call(callFunc,message,this.indexUsed,additional);
     };

     /*** Types of simple input to get  ***/
     //note: if add to any additional ones of these, then
     //must also add to if-else if statement in constructor
     //of SimpleInput
     std.core.SimpleInput.SELECT_LIST = 0;
     std.core.SimpleInput.ENTER_TEXT  = 1;

     std.core.SimpleInput.ready = function()
     {
         return haveInited;
     };

     /**
      We want to recycle the divs,etc. associ
      */
     function addToKnownUsed(simpleInputObject)
     {
         var indexToUse = null;
         for (var s in knownUsed)
         {
             if (knownUsed[s] == null)
             {
                 indexToUse = s;
                 break;
             }
         }

         //could not find empty index.  Create new element
         if (indexToUse == null)
         {
             knownUsed.push(null);
             indexToUse = knownUsed.length - 1;
         }
         knownUsed[indexToUse] = simpleInputObject;
         return indexToUse;
     }

     function getSimpleInputGUIText()
     {
         var returner = "sirikata.ui('" + SIMPLE_INPUT_MOD_NAME + "',";
         returner += 'function(){ ';

         returner += @

         //keeps track of all open windows requiring input events.
         var existingWindows = { };

         function generateDivIDFromInputID(inputID)
         {
             return 'simpleInputID__' + inputID.toString();
         }

         function newWindow(htmlForWindow,simpleInputID)
         {
             var newWindowID = generateDivIDFromInputID(simpleInputID);
             $('
'   +
               htmlForWindow   +
               '
' //end div at top.
              ).attr({id:newWindowID,title:'simpleInput'}).appendTo('body');

             var inputWindow = new sirikata.ui.window(
                 '#' + newWindowID,
                 {
	             autoOpen: true,
	             height: 'auto',
	             width: 300,
                     height: 300,
                     position: 'right'
                 }
             );
             inputWindow.show();
             existingWindows[newWindowID] = [inputWindow,simpleInputID];
         }

         simpleInputSubmit = function(simpleInputID)
         {
             var returner = null;
             //first check to see if we got the value for a text element
             var textInputID = generateSimpleInputOptionID(simpleInputID);
             var textInput = $('#' + textInputID);
             if (textInput.length > 0)
             {
                 //means that we got a text input
                 returner =textInput.val();
             }
             else
             {
                 var radioInput = $('input:radio[name=' +
                                    textInputID +']:checked');
                 if (radioInput.length > 0)
                     returner = radioInput.val();
             }

             if (returner === null)
             {
                 sirikata.log('error','In simpleInput, selecting ' +
                              'an input that does not exist.');
             }
             else
             {
                 sirikata.event('responseReceived',simpleInputID,returner);
             }

             delete existingWindows[simpleInputID];
             //actually remove the window from view after its question has been answered.
             var inputID = generateDivIDFromInputID(simpleInputID);
             $('#'+inputID).remove();
         };

         function generateSubmitButtonHtml(simpleInputID)
         {
             var htmlToDisplay = '';
             htmlToDisplay += '<button ' +
                 'onclick="simpleInputSubmit(' + simpleInputID.toString() +
                 ')">';

             htmlToDisplay += 'Submit';
             htmlToDisplay += '</button>';

             return htmlToDisplay;
         }

         simpleInputSelectList =
             function(message,simpleInputID,additional)
         {
             var htmlInsert = message + '
';
             var radName = generateSimpleInputOptionID(simpleInputID);
             for (var s in additional)
             {
                 htmlInsert += '<input type=radio name="' +
                     radName + '" value="' + additional[s][1] +
                     '"></input>' + additional[s][0];
                 htmlInsert += '
';
             }
             htmlInsert += generateSubmitButtonHtml(simpleInputID);
             newWindow(htmlInsert,simpleInputID);
         };

         function generateSimpleInputOptionID(inputID)
         {
             return 'simpleInputIDOption__' + inputID.toString();
         }

         simpleInputSelectText = function(message,simpleInputID)
         {
             var htmlInsert = message + '
';
             htmlInsert += '<input id="'                      +
                 generateSimpleInputOptionID(simpleInputID)   +
                 '"></input>
';
             htmlInsert += generateSubmitButtonHtml(simpleInputID);
             newWindow(htmlInsert,simpleInputID);

             //handles enter submitting text
             var handleEnterHit = function(evt)
             {
                 //13 represents keycode for enter, submits whatever's in
                 //the text box if user hits enter.
                 if (evt.keyCode == 13)
                     simpleInputSubmit(simpleInputID);
             };

             //copied from chat.js
             var register_area = document.getElementById(
                 generateSimpleInputOptionID(simpleInputID));
             register_area.onkeyup = handleEnterHit;
         };
         @;

         returner += '});';
         return returner;
     }

 }
)();


Add ubuntu distro to linux packaging description (#197)

<?xml version='1.0' encoding='UTF-8'?>
<project>
  <actions/>
  <description></description>
  <keepDependencies>false</keepDependencies>
  <properties>
    <com.coravy.hudson.plugins.github.GithubProjectProperty plugin="github@@1.25.1">
      <projectUrl>@(ci_scripts_repository)/</projectUrl>
      <displayName />
    </com.coravy.hudson.plugins.github.GithubProjectProperty>
    <com.sonyericsson.rebuild.RebuildSettings plugin="rebuild@@1.25">
      <autoRebuild>false</autoRebuild>
      <rebuildDisabled>false</rebuildDisabled>
    </com.sonyericsson.rebuild.RebuildSettings>
    <org.jenkinsci.plugins.requeuejob.RequeueJobProperty plugin="jobrequeue@@1.1">
      <requeueJob>true</requeueJob>
    </org.jenkinsci.plugins.requeuejob.RequeueJobProperty>
    <hudson.model.ParametersDefinitionProperty>
      <parameterDefinitions>
@(SNIPPET(
    'property_parameter-definition_common',
    ci_scripts_default_branch=ci_scripts_default_branch,
    default_repos_url=default_repos_url,
    supplemental_repos_url=supplemental_repos_url,
    ubuntu_distro=ubuntu_distro,
    cmake_build_type=cmake_build_type,
    build_args_default=build_args_default,
))@
@(SNIPPET(
    'property_parameter-definition_rmw_implementations',
    use_connext_default=use_connext_default,
    disable_connext_static_default=disable_connext_static_default,
    disable_connext_dynamic_default=disable_connext_dynamic_default,
    use_connext_debs_default=use_connext_debs_default,
    use_fastrtps_default=use_fastrtps_default,
    use_opensplice_default=use_opensplice_default,
))@
        <hudson.model.BooleanParameterDefinition>
          <name>CI_TEST_BRIDGE</name>
          <description>By setting this to True, the tests for the ros1_bridge will be run.</description>
          <defaultValue>@(test_bridge_default)</defaultValue>
        </hudson.model.BooleanParameterDefinition>
        <hudson.model.StringParameterDefinition>
          <name>CI_TEST_ARGS</name>
          <description>Additional arguments passed to the 'test' verb if testing the bridge.</description>
          <defaultValue>@(test_args_default)</defaultValue>
        </hudson.model.StringParameterDefinition>
      </parameterDefinitions>
    </hudson.model.ParametersDefinitionProperty>
  </properties>
  <scm class="hudson.plugins.git.GitSCM" plugin="git@@3.0.1">
    <configVersion>2</configVersion>
    <userRemoteConfigs>
      <hudson.plugins.git.UserRemoteConfig>
        <url>@(ci_scripts_repository)</url>
        <credentialsId>1c2004f6-2e00-425d-a421-2e1ba62ca7a7</credentialsId>
      </hudson.plugins.git.UserRemoteConfig>
    </userRemoteConfigs>
    <branches>
      <hudson.plugins.git.BranchSpec>
        <name>refs/heads/${CI_SCRIPTS_BRANCH}</name>
      </hudson.plugins.git.BranchSpec>
    </branches>
    <doGenerateSubmoduleConfigurations>false</doGenerateSubmoduleConfigurations>
    <browser class="hudson.plugins.git.browser.GithubWeb">
      <url></url>
    </browser>
    <submoduleCfg class="list"/>
    <extensions>@
@[if os_name != 'windows']
      <hudson.plugins.git.extensions.impl.SubmoduleOption>
        <disableSubmodules>false</disableSubmodules>
        <recursiveSubmodules>true</recursiveSubmodules>
        <trackingSubmodules>false</trackingSubmodules>
        <reference/>
        <parentCredentials>false</parentCredentials>
      </hudson.plugins.git.extensions.impl.SubmoduleOption>
    @
@[end if]</extensions>
  </scm>
  <assignedNode>@(label_expression)</assignedNode>
  <canRoam>false</canRoam>
  <disabled>false</disabled>
  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
  <triggers>@
@[if time_trigger_spec]
    <hudson.triggers.TimerTrigger>
      <spec>@(time_trigger_spec)</spec>
    </hudson.triggers.TimerTrigger>
  @
@[end if]</triggers>
  <concurrentBuild>true</concurrentBuild>
  <builders>
    <hudson.plugins.groovy.SystemGroovy plugin="groovy@@2.0">
      <source class="hudson.plugins.groovy.StringSystemScriptSource">

          <sandbox>false</sandbox>
        </script>
      </source>
    </hudson.plugins.groovy.SystemGroovy>
    <hudson.tasks.@(shell_type)>
      <command>@
@[if os_name in ['linux', 'linux-aarch64', 'osx']]@
rm -rf ws workspace

echo "# BEGIN SECTION: Determine arguments"
export CI_ARGS="--packaging --do-venv --force-ansi-color"
if [ -n "${CI_BRANCH_TO_TEST+x}" ]; then
  export CI_ARGS="$CI_ARGS --test-branch $CI_BRANCH_TO_TEST"
fi
if [ -n "${CI_COLCON_BRANCH+x}" ]; then
  export CI_ARGS="$CI_ARGS --colcon-branch $CI_COLCON_BRANCH"
fi
if [ "$CI_USE_CONNEXT" = "true" ]; then
  export CI_ARGS="$CI_ARGS --connext"
fi
if [ "$CI_DISABLE_CONNEXT_STATIC" = "true" ]; then
  export CI_ARGS="$CI_ARGS --disable-connext-static"
fi
if [ "$CI_DISABLE_CONNEXT_DYNAMIC" = "true" ]; then
  export CI_ARGS="$CI_ARGS --disable-connext-dynamic"
fi
if [ "$CI_USE_CONNEXT_DEBS" = "true" ]; then
  export DOCKER_BUILD_ARGS="${DOCKER_BUILD_ARGS} --build-arg INSTALL_CONNEXT_DEBS=$CI_USE_CONNEXT_DEBS"
  export CI_ARGS="$CI_ARGS --connext-debs"
fi
if [ "$CI_USE_FASTRTPS" = "true" ]; then
  export CI_ARGS="$CI_ARGS --fastrtps"
fi
if [ "$CI_USE_OPENSPLICE" = "true" ]; then
  export CI_ARGS="$CI_ARGS --opensplice"
fi
if [ -z "${CI_ROS2_REPOS_URL+x}" ]; then
  CI_ROS2_REPOS_URL="@default_repos_url"
fi
export CI_ARGS="$CI_ARGS --repo-file-url $CI_ROS2_REPOS_URL"
if [ "$CI_TEST_BRIDGE" = "true" ]; then
  export CI_ARGS="$CI_ARGS --test-bridge"
fi
if [ "${CI_UBUNTU_DISTRO}" = "bionic" ]; then
  export CI_ROS1_DISTRO=melodic
elif [ "${CI_UBUNTU_DISTRO}" = "xenial" ]; then
  export CI_ROS1_DISTRO=kinetic
fi
@[  if os_name in ['linux', 'linux-aarch64']]@
export CI_ARGS="$CI_ARGS --ros1-path /opt/ros/$CI_ROS1_DISTRO"
@[  else]@
echo "not building/testing the ros1_bridge on MacOS"
# export CI_ARGS="$CI_ARGS --ros1-path /Users/osrf/melodic/install_isolated"
@[  end if]@
if [ "${CI_CMAKE_BUILD_TYPE}" != "None" ]; then
  export CI_ARGS="$CI_ARGS --cmake-build-type $CI_CMAKE_BUILD_TYPE"
fi
if [ -n "${CI_BUILD_ARGS+x}" ]; then
  export CI_ARGS="$CI_ARGS --build-args $CI_BUILD_ARGS"
fi
if [ -n "${CI_TEST_ARGS+x}" ]; then
  export CI_ARGS="$CI_ARGS --test-args $CI_TEST_ARGS"
fi
echo "Using args: $CI_ARGS"
echo "# END SECTION"

@[  if os_name in ['linux', 'linux-aarch64']]@
sed -i "s+^FROM.*$+FROM ubuntu:$CI_UBUNTU_DISTRO+" linux_docker_resources/Dockerfile
export DOCKER_BUILD_ARGS="${DOCKER_BUILD_ARGS} --build-arg UBUNTU_DISTRO=$CI_UBUNTU_DISTRO --build-arg ROS1_DISTRO=$CI_ROS1_DISTRO"

mkdir -p $HOME/.ccache
echo "# BEGIN SECTION: docker version"
docker version
echo "# END SECTION"
echo "# BEGIN SECTION: docker info"
docker info
echo "# END SECTION"
echo "# BEGIN SECTION: Inject date into Dockerfile"
sed -i "s/@@today_str/`date +%Y-%m-%d`/" linux_docker_resources/Dockerfile
echo "# BEGIN SECTION: Build Dockerfile"
@[    if os_name == 'linux-aarch64']@
docker build ${DOCKER_BUILD_ARGS} --build-arg PLATFORM=arm --build-arg BRIDGE=true -t ros2_packaging_aarch64 linux_docker_resources
@[    elif os_name == 'linux']@
docker build ${DOCKER_BUILD_ARGS} --build-arg BRIDGE=true -t ros2_packaging linux_docker_resources
@[    else]@
@{ assert False, 'Unknown os_name: ' + os_name }@
@[    end if]@
echo "# END SECTION"
echo "# BEGIN SECTION: Run Dockerfile"
@[    if os_name == 'linux']@
export CONTAINER_NAME=ros2_packaging
@[    elif os_name == 'linux-aarch64']@
export CONTAINER_NAME=ros2_packaging_aarch64
@[    else]@
@{ assert False, 'Unknown os_name: ' + os_name }@
@[    end if]@
docker run --rm --privileged -e UID=`id -u` -e GID=`id -g` -e CI_ARGS="$CI_ARGS" -e CCACHE_DIR=/home/rosbuild/.ccache -i -v `pwd`:/home/rosbuild/ci_scripts -v $HOME/.ccache:/home/rosbuild/.ccache $CONTAINER_NAME
echo "# END SECTION"
@[  else]@
echo "# BEGIN SECTION: Run packaging script"
/usr/local/bin/python3 -u run_ros2_batch.py $CI_ARGS
echo "# END SECTION"
@[  end if]@
@[elif os_name == 'windows']@
setlocal enableDelayedExpansion
rmdir /S /Q ws workspace

echo "# BEGIN SECTION: Determine arguments"
set "PATH=!PATH:"=!"
set "CI_ARGS=--packaging --force-ansi-color"
if "!CI_BRANCH_TO_TEST!" NEQ "" (
  set "CI_ARGS=!CI_ARGS! --test-branch !CI_BRANCH_TO_TEST!"
)
if "!CI_COLCON_BRANCH!" NEQ "" (
  set "CI_ARGS=!CI_ARGS! --colcon-branch !CI_COLCON_BRANCH!"
)
if "!CI_USE_CONNEXT!" == "true" (
  set "CI_ARGS=!CI_ARGS! --connext"
)
if "!CI_DISABLE_CONNEXT_STATIC!" == "true" (
  set "CI_ARGS=!CI_ARGS! --disable-connext-static"
)
if "!CI_DISABLE_CONNEXT_DYNAMIC!" == "true" (
  set "CI_ARGS=!CI_ARGS! --disable-connext-dynamic"
)
if "!CI_USE_CONNEXT_DEBS!" == "true" (
  set "CI_ARGS=!CI_ARGS! --connext-debs"
)
if "!CI_USE_FASTRTPS!" == "true" (
  set "CI_ARGS=!CI_ARGS! --fastrtps"
)
if "!CI_USE_OPENSPLICE!" == "true" (
  set "CI_ARGS=!CI_ARGS! --opensplice"
)
if "!CI_ROS2_REPOS_URL!" EQU "" (
  set "CI_ROS2_REPOS_URL=@default_repos_url"
)
set "CI_ARGS=!CI_ARGS! --repo-file-url !CI_ROS2_REPOS_URL!"
if "!CI_TEST_BRIDGE!" == "true" (
  set "CI_ARGS=!CI_ARGS! --test-bridge"
)
if "!CI_CMAKE_BUILD_TYPE!" NEQ "None" (
  set "CI_ARGS=!CI_ARGS! --cmake-build-type !CI_CMAKE_BUILD_TYPE!"
)
if "!CI_BUILD_ARGS!" NEQ "" (
  set "CI_ARGS=!CI_ARGS! --build-args !CI_BUILD_ARGS!"
)
if "!CI_TEST_ARGS!" NEQ "" (
  set "CI_ARGS=!CI_ARGS! --test-args !CI_TEST_ARGS!"
)
echo Using args: !CI_ARGS!
echo "# END SECTION"

echo "# BEGIN SECTION: Run packaging script"
python -u run_ros2_batch.py !CI_ARGS!
echo "# END SECTION"
@[else]@
@{ assert False, 'Unknown os_name: ' + os_name }@
@[end if]</command>
    </hudson.tasks.@(shell_type)>
  </builders>
  <publishers>
@(SNIPPET(
    'publisher_warnings',
    os_name=os_name,
))@
@(SNIPPET(
    'publisher_cobertura',
))@
@(SNIPPET(
    'publisher_xunit',
))@
    <hudson.tasks.ArtifactArchiver>
      <artifacts>ws/ros2-package-*.*</artifacts>
      <allowEmptyArchive>false</allowEmptyArchive>
      <onlyIfSuccessful>true</onlyIfSuccessful>
      <fingerprint>false</fingerprint>
      <defaultExcludes>true</defaultExcludes>
      <caseSensitive>true</caseSensitive>
    </hudson.tasks.ArtifactArchiver>
@[if mailer_recipients]@
    <hudson.tasks.Mailer plugin="mailer@@1.20">
      <recipients>@(mailer_recipients)</recipients>
      <dontNotifyEveryUnstableBuild>false</dontNotifyEveryUnstableBuild>
      <sendToIndividuals>false</sendToIndividuals>
    </hudson.tasks.Mailer>
@[end if]@
  </publishers>
  <buildWrappers>
    <hudson.plugins.timestamper.TimestamperBuildWrapper plugin="timestamper@@1.8.8" />
    <hudson.plugins.ansicolor.AnsiColorBuildWrapper plugin="ansicolor@@0.5.0">
      <colorMapName>xterm</colorMapName>
    </hudson.plugins.ansicolor.AnsiColorBuildWrapper>
@[if os_name != 'windows']@
    <com.cloudbees.jenkins.plugins.sshagent.SSHAgentBuildWrapper plugin="ssh-agent@@1.15">
      <credentialIds>
        <string>1c2004f6-2e00-425d-a421-2e1ba62ca7a7</string>
      </credentialIds>
      <ignoreMissing>false</ignoreMissing>
    </com.cloudbees.jenkins.plugins.sshagent.SSHAgentBuildWrapper>
@[end if]@
  </buildWrappers>
</project>