// Test not yet to scale re: data points.
TEST_F(ProjectFixture,Profile_ClearAnalysisResults) {
  ProjectDatabase db = getPopulatedDatabase(100,500,true,toPath("./ClearAnalysisResults"));

  // mimic code in AnalysisDriver to avoid adding AnalysisDriver as a dependency

  // time the process of retrieving response function data
  ptime start = microsec_clock::local_time();
  db.unloadUnusedCleanRecords();
  ASSERT_TRUE(db.startTransaction());
  DataPointRecordVector dataPointRecords = DataPointRecord::getDataPointRecords(db);
  EXPECT_EQ(500u,dataPointRecords.size());
  BOOST_FOREACH(project::DataPointRecord& dataPointRecord,dataPointRecords) {
    // in AnalysisDriver, removes DataPoint directories, but they don't exist here
    db.removeRecord(dataPointRecord);
  }
void RubyContinuousVariableRecord::constructRelatedRecords(const analysis::Variable& variable)
{
  analysis::RubyContinuousVariable rubyContinuousVariable = variable.cast<analysis::RubyContinuousVariable>();
  RubyContinuousVariableRecord copyOfThis(getImpl<detail::RubyContinuousVariableRecord_Impl>());
  ProjectDatabase database = projectDatabase();
  bool isNew = database.isNewRecord(copyOfThis);
  if (!isNew) {
    getImpl<detail::RubyContinuousVariableRecord_Impl>()->revertToLastRecordIds();
  }

  // Save resource RubyMeasure
  if (isNew || rubyContinuousVariable.perturbation().isDirty()) {
    RubyMeasureRecord rubyMeasureRecord(rubyContinuousVariable.measure(),database);
    getImpl<detail::RubyContinuousVariableRecord_Impl>()->setRubyMeasureRecordId(rubyMeasureRecord.id());
  }

  // Save child OSArgument
  OSArgument argument = rubyContinuousVariable.argument();
  bool saveArgument = false;
  if (!isNew) {
    {
      // see if old argument record should be removed
      OSArgumentRecord oldArgumentRecord = osArgumentRecord();
      if (oldArgumentRecord.handle() != argument.uuid()) {
        database.removeRecord(oldArgumentRecord);
        saveArgument = true;
      }
      else if (oldArgumentRecord.uuidLast() != argument.versionUUID()) {
        saveArgument = true;
      }
    }
    database.unloadUnusedCleanRecords();
  }
  if (saveArgument || isNew) {
    OSArgumentRecord osArgumentRecord(argument,copyOfThis);
  }
}
// Test not yet to scale re: total data points.
TEST_F(ProjectFixture,Profile_UpdateAnalysis) {
  Analysis analysis = getAnalysisToRun(100,500);

  // save to database
  ProjectDatabase db = getCleanDatabase(toPath("./UpdateAnalysis"));
  ASSERT_TRUE(db.startTransaction());
  AnalysisRecord record(analysis,db);
  db.save();
  ASSERT_TRUE(db.commitTransaction());

  // add output data to 1 data point
  DataPointVector dataPoints = analysis.dataPoints();
  boost::mt19937 mt;
  typedef boost::uniform_real<> uniform_dist_type;
  typedef boost::variate_generator<boost::mt19937&, uniform_dist_type> uniform_gen_type;
  uniform_gen_type responseGenerator(mt,uniform_dist_type(50.0,500.0));
  for (int i = 0; i < 1; ++i) {
    std::stringstream ss;
    ss << "dataPoint" << i + 1;
    DoubleVector responseValues;
    for (int j = 0, n = analysis.problem().responses().size(); j < n; ++j) {
      responseValues.push_back(responseGenerator());
    }
    openstudio::path runDir = toPath(ss.str());
    dataPoints[i] = DataPoint(dataPoints[i].uuid(),
                              createUUID(),
                              dataPoints[i].name(),
                              dataPoints[i].displayName(),
                              dataPoints[i].description(),
                              analysis.problem(),
                              true,
                              false,
                              true,
                              DataPointRunType::Local,
                              dataPoints[i].variableValues(),
                              responseValues,
                              runDir,
                              FileReference(runDir / toPath("ModelToIdf/in.osm")),
                              FileReference(runDir / toPath("ModelToIdf/out.idf")),
                              FileReference(runDir / toPath("EnergyPlus/eplusout.sql")),
                              FileReferenceVector(1u,FileReference(runDir / toPath("Ruby/report.xml"))),
                              boost::optional<runmanager::Job>(),
                              std::vector<openstudio::path>(),
                              TagVector(),
                              AttributeVector());
    dataPoints[i].setName(dataPoints[i].name()); // set dirty
  }
  analysis = Analysis(analysis.uuid(),
                      analysis.versionUUID(),
                      analysis.name(),
                      analysis.displayName(),
                      analysis.description(),
                      analysis.problem(),
                      analysis.algorithm(),
                      analysis.seed(),
                      analysis.weatherFile(),
                      dataPoints,
                      false,
                      false);
  analysis.setName(analysis.name()); // set dirty

  // time the process of updating the database
  ptime start = microsec_clock::local_time();
  db.unloadUnusedCleanRecords();
  ASSERT_TRUE(db.startTransaction());
  record = AnalysisRecord(analysis,db);
  db.save();
  ASSERT_TRUE(db.commitTransaction());
  time_duration updateTime = microsec_clock::local_time() - start;

  std::cout << "Time: " << to_simple_string(updateTime) << std::endl;
}
Пример #4
0
TEST_F(ProjectFixture,DataPoint_DataPointRecord_Synchronization) {

  // create analysis with one data point
  ProjectDatabase database = getCleanDatabase("DataPoint_DataPointRecord_Synchronization");

  analysis::Analysis analysis(
      "Test Analysis",
      analysis::Problem("Test Problem",analysis::VariableVector(),runmanager::Workflow()),
      analysis::DesignOfExperiments(analysis::DesignOfExperimentsOptions(
          analysis::DesignOfExperimentsType::FullFactorial)),
      FileReference(toPath("in.osm")));

  std::vector<QVariant> variableValues;
  analysis::DataPoint original = analysis.problem().createDataPoint(variableValues).get();
  analysis.addDataPoint(original);

  // save analysis to database
  {
    bool didStartTransaction = database.startTransaction();
    AnalysisRecord analysisRecord(analysis,database);

    DataPointRecordVector dataPointRecords = DataPointRecord::getDataPointRecords(database);
    ASSERT_EQ(1u,dataPointRecords.size());
    DataPointRecord dataPointRecord = dataPointRecords[0];
    EXPECT_FALSE(dataPointRecord.isComplete());

    database.save();
    if (didStartTransaction) {
      database.commitTransaction();
    }
  }

  // reload analysis, change data point, save back to database
  // verify that change shows up in database
  OptionalAnalysis loadedAnalysis;
  {
    AnalysisRecordVector loadedAnalysisRecords = AnalysisRecord::getAnalysisRecords(database);
    ASSERT_EQ(1u,loadedAnalysisRecords.size());
    loadedAnalysis = loadedAnalysisRecords[0].analysis();

    analysis::DataPointVector loadedDataPoints = loadedAnalysis->dataPoints();
    ASSERT_EQ(1u,loadedDataPoints.size());
    loadedDataPoints[0].getImpl<analysis::detail::DataPoint_Impl>()->markComplete();
    EXPECT_TRUE(loadedDataPoints[0].isComplete());
  }
  ASSERT_TRUE(loadedAnalysis);

  {
    bool didStartTransaction = database.startTransaction();
    database.unloadUnusedCleanRecords();
    AnalysisRecord analysisRecord(*loadedAnalysis,database);
    database.save();
    if (didStartTransaction) {
      database.commitTransaction();
    }
    EXPECT_EQ(1u,AnalysisRecord::getAnalysisRecords(database).size());

    DataPointRecordVector dataPointRecords = DataPointRecord::getDataPointRecords(database);
    ASSERT_EQ(1u,dataPointRecords.size());
    DataPointRecord dataPointRecord = dataPointRecords[0];
    EXPECT_TRUE(dataPointRecord.isComplete());
  }

}
Пример #5
0
TEST_F(ProjectFixture,AnalysisRecord_AddAndRemoveDataPoints) {
  // create an analysis with data points, no results
  Analysis analysis("My Analysis",
                    Problem("My Problem",VariableVector(),runmanager::Workflow()),
                    FileReferenceType::OSM);
  Problem problem = analysis.problem();

  DiscretePerturbationVector perturbations;
  int pi = 1;
  std::stringstream ss;
  for (int i = 0; i < 3; ++i) {
    perturbations.push_back(NullPerturbation());
    for (int j = 0; j < 4; ++j) {
      ss << "measure" << pi << ".rb";
      perturbations.push_back(RubyPerturbation(toPath(ss.str()),
                                               FileReferenceType::OSM,
                                               FileReferenceType::OSM,true));
      ss.str("");
      ++pi;
    }
    ss << "Variable " << i+1;
    problem.push(DiscreteVariable(ss.str(),perturbations));
    perturbations.clear();
    ss.str("");
  }

  EXPECT_EQ(3u,analysis.problem().variables().size());
  ASSERT_FALSE(problem.variables().empty());
  EXPECT_EQ(5u,problem.variables()[0].cast<DiscreteVariable>().numPerturbations(true));

  problem.pushResponse(
        LinearFunction("Energy Use",
                       VariableVector(1u,OutputAttributeVariable("Energy Use","Total.Energy.Use"))));
  EXPECT_EQ(1u,problem.responses().size());

  std::vector<QVariant> values(3u,0);
  OptionalDataPoint dataPoint = problem.createDataPoint(values);
  ASSERT_TRUE(dataPoint);
  bool test = analysis.addDataPoint(*dataPoint);
  EXPECT_TRUE(test);
  values[1] = 3;
  dataPoint = problem.createDataPoint(values);
  ASSERT_TRUE(dataPoint);
  test = analysis.addDataPoint(*dataPoint);
  EXPECT_TRUE(test);
  values[0] = 1;
  values[1] = 2;
  values[2] = 4;
  dataPoint = problem.createDataPoint(values);
  ASSERT_TRUE(dataPoint);
  test = analysis.addDataPoint(*dataPoint);
  EXPECT_TRUE(test);

  EXPECT_TRUE(analysis.isDirty());
  EXPECT_TRUE(problem.isDirty());
  BOOST_FOREACH(const Variable& variable, problem.variables()) {
    EXPECT_TRUE(variable.isDirty());
    ASSERT_TRUE(variable.optionalCast<DiscreteVariable>());
    BOOST_FOREACH(const DiscretePerturbation& perturbation,variable.cast<DiscreteVariable>().perturbations(false)) {
      EXPECT_TRUE(perturbation.isDirty());
    }
  }
  BOOST_FOREACH(const Function& response, problem.responses()) {
    EXPECT_TRUE(response.isDirty());
  }
  BOOST_FOREACH(const DataPoint& dataPoint, analysis.dataPoints()) {
    EXPECT_TRUE(dataPoint.isDirty());
  }

  // save to database, make sure everything is there, make sure clean signal filters down
  ProjectDatabase database = getCleanDatabase("AnalysisRecord_AddAndRemoveDataPoints");
  {
    bool transactionStarted = database.startTransaction();
    EXPECT_TRUE(transactionStarted);
    AnalysisRecord analysisRecord(analysis,database);
    database.save();
    test = database.commitTransaction();
    EXPECT_TRUE(test);
    EXPECT_EQ(analysis.uuid(),analysisRecord.handle());
    EXPECT_EQ(analysis.versionUUID(),analysisRecord.uuidLast());
    ASSERT_NO_THROW(analysisRecord.problemRecord());
    ProblemRecord problemRecord = analysisRecord.problemRecord();
    InputVariableRecordVector variableRecords = problemRecord.inputVariableRecords();
    EXPECT_EQ(3u,variableRecords.size());
    ASSERT_FALSE(variableRecords.empty());
    ASSERT_TRUE(variableRecords[0].optionalCast<DiscreteVariableRecord>());
    DiscretePerturbationRecordVector perturbationRecords = variableRecords[0].cast<DiscreteVariableRecord>().discretePerturbationRecords(false);
    EXPECT_EQ(5u,perturbationRecords.size());
    ASSERT_TRUE(perturbationRecords.size() > 1);
    EXPECT_TRUE(perturbationRecords[0].optionalCast<NullPerturbationRecord>());
    EXPECT_TRUE(perturbationRecords[1].optionalCast<RubyPerturbationRecord>());
    EXPECT_EQ(3u,analysisRecord.dataPointRecords().size());
    EXPECT_TRUE(analysisRecord.completeDataPointRecords().empty());
  }

  analysis.clearDirtyFlag();
  EXPECT_FALSE(analysis.isDirty());
  EXPECT_FALSE(problem.isDirty());
  BOOST_FOREACH(const Variable& variable, problem.variables()) {
    EXPECT_FALSE(variable.isDirty());
    ASSERT_TRUE(variable.optionalCast<DiscreteVariable>());
    BOOST_FOREACH(const DiscretePerturbation& perturbation,variable.cast<DiscreteVariable>().perturbations(false)) {
      EXPECT_FALSE(perturbation.isDirty());
    }
  }
  BOOST_FOREACH(const Function& response, problem.responses()) {
    EXPECT_FALSE(response.isDirty());
  }
  BOOST_FOREACH(const DataPoint& dataPoint, analysis.dataPoints()) {
    EXPECT_FALSE(dataPoint.isDirty());
  }

  // make some more data points and make sure dirty flag bubbles up
  values[0] = 3;
  values[1] = 2;
  values[2] = 2;
  dataPoint = problem.createDataPoint(values);
  ASSERT_TRUE(dataPoint);
  test = analysis.addDataPoint(*dataPoint);
  EXPECT_TRUE(test);
  values[0] = 0;
  values[1] = 4;
  values[2] = 0;
  dataPoint = problem.createDataPoint(values);
  ASSERT_TRUE(dataPoint);
  test = analysis.addDataPoint(*dataPoint);
  EXPECT_TRUE(test);
  EXPECT_EQ(5u,analysis.dataPoints().size());
  EXPECT_TRUE(analysis.isDirty());
  EXPECT_FALSE(problem.isDirty());
  BOOST_FOREACH(const Variable& variable, problem.variables()) {
    EXPECT_FALSE(variable.isDirty());
    ASSERT_TRUE(variable.optionalCast<DiscreteVariable>());
    BOOST_FOREACH(const DiscretePerturbation& perturbation,variable.cast<DiscreteVariable>().perturbations(false)) {
      EXPECT_FALSE(perturbation.isDirty());
    }
  }
  BOOST_FOREACH(const Function& response, problem.responses()) {
    EXPECT_FALSE(response.isDirty());
  }
  int i = 0;
  BOOST_FOREACH(const DataPoint& dataPoint, analysis.dataPoints()) {
    if (i < 3) {
      EXPECT_FALSE(dataPoint.isDirty());
    }
    else {
      EXPECT_TRUE(dataPoint.isDirty());
    }
    ++i;
  }

  // save to database, make sure changes made it in
  {
    bool transactionStarted = database.startTransaction();
    EXPECT_TRUE(transactionStarted);
    database.unloadUnusedCleanRecords(); // ETH@20130201 - Having to call this is awkward.
                                         // Where is the best place?
    AnalysisRecord analysisRecord(analysis,database);
    database.save();
    test = database.commitTransaction();
    EXPECT_TRUE(test);
    EXPECT_EQ(analysis.uuid(),analysisRecord.handle());
    EXPECT_EQ(analysis.versionUUID(),analysisRecord.uuidLast());
    ASSERT_NO_THROW(analysisRecord.problemRecord());
    ProblemRecord problemRecord = analysisRecord.problemRecord();
    EXPECT_EQ(problemRecord.handle(),analysisRecord.problemRecord().handle());
    EXPECT_EQ(problemRecord.uuidLast(),analysisRecord.problemRecord().uuidLast());
    InputVariableRecordVector variableRecords = problemRecord.inputVariableRecords();
    EXPECT_EQ(3u,variableRecords.size());
    ASSERT_FALSE(variableRecords.empty());
    ASSERT_TRUE(variableRecords[0].optionalCast<DiscreteVariableRecord>());
    DiscretePerturbationRecordVector perturbationRecords = variableRecords[0].cast<DiscreteVariableRecord>().discretePerturbationRecords(false);
    EXPECT_EQ(5u,perturbationRecords.size());
    ASSERT_TRUE(perturbationRecords.size() > 1);
    EXPECT_TRUE(perturbationRecords[0].optionalCast<NullPerturbationRecord>());
    EXPECT_TRUE(perturbationRecords[1].optionalCast<RubyPerturbationRecord>());
    EXPECT_EQ(5u,analysisRecord.dataPointRecords().size());
    EXPECT_TRUE(analysisRecord.completeDataPointRecords().empty());
  }

  analysis.clearDirtyFlag();
  EXPECT_FALSE(analysis.isDirty());
  EXPECT_FALSE(problem.isDirty());
  BOOST_FOREACH(const Variable& variable, problem.variables()) {
    EXPECT_FALSE(variable.isDirty());
    ASSERT_TRUE(variable.optionalCast<DiscreteVariable>());
    BOOST_FOREACH(const DiscretePerturbation& perturbation,variable.cast<DiscreteVariable>().perturbations(false)) {
      EXPECT_FALSE(perturbation.isDirty());
    }
  }
  BOOST_FOREACH(const Function& response, problem.responses()) {
    EXPECT_FALSE(response.isDirty());
  }
  BOOST_FOREACH(const DataPoint& dataPoint, analysis.dataPoints()) {
    EXPECT_FALSE(dataPoint.isDirty());
  }

  // remove a data point and save database. make sure point actually disappears
  DataPoint toRemove = analysis.dataPoints()[3];
  test = analysis.removeDataPoint(toRemove);
  EXPECT_TRUE(test);
  EXPECT_TRUE(analysis.isDirty());

  {
    bool transactionStarted = database.startTransaction();
    EXPECT_TRUE(transactionStarted);
    database.unloadUnusedCleanRecords(); // ETH@20130201 - Having to call this is awkward.
    // Where is the best place?
    AnalysisRecord analysisRecord(analysis,database);
    database.save();
    test = database.commitTransaction();
    EXPECT_TRUE(test);
    EXPECT_EQ(analysis.uuid(),analysisRecord.handle());
    EXPECT_EQ(analysis.versionUUID(),analysisRecord.uuidLast());
    ASSERT_NO_THROW(analysisRecord.problemRecord());
    ProblemRecord problemRecord = analysisRecord.problemRecord();
    EXPECT_EQ(problemRecord.handle(),analysisRecord.problemRecord().handle());
    EXPECT_EQ(problemRecord.uuidLast(),analysisRecord.problemRecord().uuidLast());
    InputVariableRecordVector variableRecords = problemRecord.inputVariableRecords();
    EXPECT_EQ(3u,variableRecords.size());
    ASSERT_FALSE(variableRecords.empty());
    ASSERT_TRUE(variableRecords[0].optionalCast<DiscreteVariableRecord>());
    DiscretePerturbationRecordVector perturbationRecords = variableRecords[0].cast<DiscreteVariableRecord>().discretePerturbationRecords(false);
    EXPECT_EQ(5u,perturbationRecords.size());
    ASSERT_TRUE(perturbationRecords.size() > 1);
    EXPECT_TRUE(perturbationRecords[0].optionalCast<NullPerturbationRecord>());
    EXPECT_TRUE(perturbationRecords[1].optionalCast<RubyPerturbationRecord>());
    EXPECT_EQ(4u,analysisRecord.dataPointRecords().size());
    OptionalDataPointRecord searchResult = database.getObjectRecordByHandle<DataPointRecord>(toRemove.uuid());
    EXPECT_FALSE(searchResult);
    EXPECT_EQ(4u,DataPointRecord::getDataPointRecords(database).size());
  }

  EXPECT_EQ(4u,analysis.dataPoints().size());
  test = analysis.clearDirtyFlag();
  EXPECT_TRUE(test);
}
Пример #6
0
TEST_F(ProjectFixture,AnalysisRecord_SetProblem) {
  // create an analysis with data points
  Problem problem1("Minimal Problem",VariableVector(),runmanager::Workflow());
  Analysis analysis("Analysis",
                    problem1,
                    FileReferenceType::OSM);
  OptionalDataPoint dataPoint = problem1.createDataPoint(std::vector<QVariant>());
  ASSERT_TRUE(dataPoint);
  bool test = analysis.addDataPoint(*dataPoint);
  EXPECT_TRUE(test);

  // save to database, make sure everything is there, make sure clean signal filters down
  ProjectDatabase database = getCleanDatabase("AnalysisRecord_SetProblem");
  {
    bool transactionStarted = database.startTransaction();
    EXPECT_TRUE(transactionStarted);
    AnalysisRecord analysisRecord(analysis,database);
    database.save();
    test = database.commitTransaction();
    EXPECT_TRUE(test);
    ASSERT_NO_THROW(analysisRecord.problemRecord());
    ProblemRecord problemRecord = analysisRecord.problemRecord();
    EXPECT_EQ(problem1.uuid(),problemRecord.handle());
    EXPECT_EQ(problem1.versionUUID(),problemRecord.uuidLast());
    EXPECT_EQ(1u,analysisRecord.dataPointRecords().size());
  }
  analysis.clearDirtyFlag();
  EXPECT_FALSE(analysis.isDirty());
  EXPECT_FALSE(problem1.isDirty());
  BOOST_FOREACH(const DataPoint& dataPoint, analysis.dataPoints()) {
    EXPECT_FALSE(dataPoint.isDirty());
  }
  EXPECT_FALSE(analysis.dataPointsAreInvalid());

  // set analysis to have new problem
  // make sure dirty flag bubbles up, data points marked invalid
  Problem problem2 = problem1.clone().cast<Problem>();
  problem2.setName("Real Problem");
  EXPECT_NE(problem1.uuid(),problem2.uuid());
  analysis.setProblem(problem2);
  EXPECT_TRUE(analysis.isDirty());
  EXPECT_FALSE(problem1.isDirty());
  EXPECT_TRUE(problem2.isDirty());
  EXPECT_TRUE(analysis.dataPointsAreInvalid());
  RubyPerturbation userScript(toPath("measure.rb"),
                              FileReferenceType::IDF,
                              FileReferenceType::IDF,
                              true);
  RubyContinuousVariable cvar("Script Argument Variable",
                              ruleset::OSArgument::makeDoubleArgument("COP"),
                              userScript);
  test = problem2.push(cvar);
  EXPECT_FALSE(test); // IDF not compatible with seed
  EXPECT_TRUE(problem2.variables().empty());
  test = userScript.setPerturbationScript(toPath("measure.rb"),
                                          FileReferenceType::OSM,
                                          FileReferenceType::OSM,
                                          true);
  EXPECT_TRUE(test);
  test = problem2.push(cvar);
  EXPECT_TRUE(test);
  EXPECT_EQ(1u,analysis.problem().variables().size());
  EXPECT_EQ(1u,analysis.dataPoints().size());
  dataPoint = problem2.createDataPoint(std::vector<QVariant>(1u,3.5));
  ASSERT_TRUE(dataPoint);
  test = analysis.addDataPoint(*dataPoint);
  EXPECT_FALSE(test);

  // save to database, make sure dataPointsAreInvalid flag was saved, old problem
  // should still be there, new problem there, and clean signal won't go through
  Analysis analysisCopy = analysis; // will replace with deserialized version momentarily
  {
    bool transactionStarted = database.startTransaction();
    EXPECT_TRUE(transactionStarted);
    database.unloadUnusedCleanRecords(); // ETH@20130201 - Having to call this is awkward.
                                         // (As are the brackets) Where is the best place?
    AnalysisRecord analysisRecord(analysis,database);
    database.save();
    test = database.commitTransaction();
    EXPECT_TRUE(test);
    ASSERT_NO_THROW(analysisRecord.problemRecord());
    ProblemRecord problemRecord = analysisRecord.problemRecord();
    EXPECT_EQ(problem2.uuid(),problemRecord.handle());
    EXPECT_EQ(problem2.versionUUID(),problemRecord.uuidLast());
    EXPECT_EQ(2u,ProblemRecord::getProblemRecords(database).size()); // old one still around
    EXPECT_EQ(1u,analysisRecord.dataPointRecords().size());
    test = analysis.clearDirtyFlag();
    EXPECT_FALSE(test);
    Analysis analysisCopy = analysisRecord.analysis(); // should work b/c both problems around
  }
  EXPECT_EQ(analysis.uuid(),analysisCopy.uuid());
  EXPECT_EQ(analysis.versionUUID(),analysisCopy.versionUUID());
  EXPECT_TRUE(analysisCopy.isDirty());
  EXPECT_TRUE(analysisCopy.dataPointsAreInvalid());
  ASSERT_FALSE(analysisCopy.dataPoints().empty());
  EXPECT_NE(analysisCopy.problem().uuid(),analysisCopy.dataPoints()[0].problem().uuid());
  test = analysisCopy.clearDirtyFlag();
  EXPECT_FALSE(test);

  // remove data points, save again, make sure dataPointsAreInvalid flag a-okay, new problem
  // still there and ok, old problem is going to hang around because didn't get rid of it when
  // analysisRecord still pointing to it, clean signal filters all the way down
  analysisCopy.removeAllDataPoints();
  {
    bool transactionStarted = database.startTransaction();
    EXPECT_TRUE(transactionStarted);
    database.unloadUnusedCleanRecords(); // ETH@20130201 - Having to call this is awkward.
                                         // Where is the best place?
    AnalysisRecord analysisRecord(analysisCopy,database);
    database.save();
    test = database.commitTransaction();
    EXPECT_TRUE(test);
    ASSERT_NO_THROW(analysisRecord.problemRecord());
    ProblemRecord problemRecord = analysisRecord.problemRecord();
    EXPECT_EQ(problem2.uuid(),problemRecord.handle());
    EXPECT_EQ(problem2.versionUUID(),problemRecord.uuidLast());
    // old problem record still hanging around.
    // TODO: Have use count as child, resource, and be able to (selectively) purge.
    EXPECT_EQ(2u,ProblemRecord::getProblemRecords(database).size());
    EXPECT_TRUE(analysisRecord.dataPointRecords().empty());
    analysis = analysisRecord.analysis();
  }
  test = analysis.clearDirtyFlag();
  EXPECT_TRUE(test);
  EXPECT_FALSE(analysis.isDirty());
  EXPECT_FALSE(analysis.dataPointsAreInvalid());
  EXPECT_FALSE(analysis.resultsAreInvalid());
  EXPECT_TRUE(analysis.dataPoints().empty());
}