<commit_before>// BugReporter.cpp - Generate PathDiagnostics for Bugs ------------*- C++ -*--//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
//  This file defines BugReporter, a utility class for generating
//  PathDiagnostics for analyses based on GRSimpleVals.
//
//===----------------------------------------------------------------------===//

#include "clang/Analysis/PathSensitive/BugReporter.h"
#include "clang/Analysis/PathSensitive/GRExprEngine.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/CFG.h"
#include "clang/AST/Expr.h"
#include "clang/Analysis/ProgramPoint.h"
#include "clang/Analysis/PathDiagnostic.h"
#include <sstream>

using namespace clang;

BugReporter::~BugReporter() {}
BugType::~BugType() {}
BugReport::~BugReport() {}
RangedBugReport::~RangedBugReport() {}

ExplodedGraph<ValueState>& BugReporter::getGraph() { return Eng.getGraph(); }

static inline Stmt* GetStmt(const ProgramPoint& P) {
  if (const PostStmt* PS = dyn_cast<PostStmt>(&P)) {
    return PS->getStmt();
  }
  else if (const BlockEdge* BE = dyn_cast<BlockEdge>(&P)) {
    return BE->getSrc()->getTerminator();
  }
  else if (const BlockEntrance* BE = dyn_cast<BlockEntrance>(&P)) {
    return BE->getFirstStmt();
  }

  assert (false && "Unsupported ProgramPoint.");
  return NULL;
}

static inline Stmt* GetStmt(const CFGBlock* B) {
  assert (!B->empty());
  return (*B)[0];
}

Stmt* BugReport::getStmt() const {
  return N ? GetStmt(N->getLocation()) : NULL;
}

static inline ExplodedNode<ValueState>*
GetNextNode(ExplodedNode<ValueState>* N) {
  return N->pred_empty() ? NULL : *(N->pred_begin());
}

static Stmt* GetLastStmt(ExplodedNode<ValueState>* N) {
  assert (isa<BlockEntrance>(N->getLocation()));

  for (N = GetNextNode(N); N; N = GetNextNode(N)) {

    ProgramPoint P = N->getLocation();

    if (PostStmt* PS = dyn_cast<PostStmt>(&P))
      return PS->getStmt();
  }

  return NULL;
}

PathDiagnosticPiece*
BugReport::getEndPath(BugReporter& BR,
                      ExplodedNode<ValueState>* EndPathNode) const {

  ProgramPoint ProgP = EndPathNode->getLocation();
  Stmt *S = NULL;

  if (BlockEntrance* BE = dyn_cast<BlockEntrance>(&ProgP))
    if (BE->getBlock() == &BR.getCFG().getExit())
      S = GetLastStmt(EndPathNode);
  if (!S)
    S = GetStmt(ProgP);

  if (!S)
    return NULL;

  FullSourceLoc L(S->getLocStart(), BR.getContext().getSourceManager());

  PathDiagnosticPiece* P =
    new PathDiagnosticPiece(L, getDescription());

  const SourceRange *Beg, *End;
  getRanges(Beg, End);

  if (Beg == End) {
    if (Expr* E = dyn_cast<Expr>(S))
      P->addRange(E->getSourceRange());
  }
  else {
    assert (Beg < End);
    for (; Beg != End; ++Beg)
      P->addRange(*Beg);
  }

  return P;
}

void BugReport::getRanges(const SourceRange*& beg,
                          const SourceRange*& end) const {
  beg = NULL;
  end = NULL;
}

FullSourceLoc BugReport::getLocation(SourceManager& Mgr) {

  if (!N)
    return FullSourceLoc();

  Stmt* S = GetStmt(N->getLocation());

  if (!S)
    return FullSourceLoc();

  return FullSourceLoc(S->getLocStart(), Mgr);
}

PathDiagnosticPiece* BugReport::VisitNode(ExplodedNode<ValueState>* N,
                                          ExplodedNode<ValueState>* PrevN,
                                          ExplodedGraph<ValueState>& G,
                                          ASTContext& Ctx) {
  return NULL;
}

void BugReporter::GeneratePathDiagnostic(PathDiagnostic& PD,
                                         BugReport& R) {

  ExplodedNode<ValueState>* N = R.getEndNode();

  if (!N)
    return;

  llvm::OwningPtr<ExplodedGraph<ValueState> > GTrim(getGraph().Trim(&N, &N+1));

  // Find the sink in the trimmed graph.
  // FIXME: Should we eventually have a sink iterator?

  ExplodedNode<ValueState>* NewN = 0;

  for (ExplodedGraph<ValueState>::node_iterator
       I = GTrim->nodes_begin(), E = GTrim->nodes_end(); I != E; ++I) {

    if (I->isSink()) {
      NewN = &*I;
      break;
    }
  }

  assert (NewN);
  assert (NewN->getLocation() == N->getLocation());

  N = NewN;

  if (PathDiagnosticPiece* Piece = R.getEndPath(*this, N))
    PD.push_back(Piece);
  else
    return;

  ExplodedNode<ValueState>* NextNode = N->pred_empty()
                                       ? NULL : *(N->pred_begin());

  SourceManager& SMgr = Ctx.getSourceManager();

  while (NextNode) {

    ExplodedNode<ValueState>* LastNode = N;
    N = NextNode;
    NextNode = GetNextNode(N);

    ProgramPoint P = N->getLocation();

    if (const BlockEdge* BE = dyn_cast<BlockEdge>(&P)) {

      CFGBlock* Src = BE->getSrc();
      CFGBlock* Dst = BE->getDst();

      Stmt* T = Src->getTerminator();

      if (!T)
        continue;

      FullSourceLoc L(T->getLocStart(), SMgr);

      switch (T->getStmtClass()) {
        default:
          break;

        case Stmt::GotoStmtClass:
        case Stmt::IndirectGotoStmtClass: {

          Stmt* S = GetStmt(LastNode->getLocation());

          if (!S)
            continue;

          std::ostringstream os;

          os << "Control jumps to line "
             << SMgr.getLogicalLineNumber(S->getLocStart()) << ".\n";

          PD.push_front(new PathDiagnosticPiece(L, os.str()));
          break;
        }

        case Stmt::SwitchStmtClass: {

          // Figure out what case arm we took.

          Stmt* S = Dst->getLabel();

          if (!S)
            continue;

          std::ostringstream os;

          switch (S->getStmtClass()) {
            default:
              continue;

            case Stmt::DefaultStmtClass: {

              os << "Control jumps to the 'default' case at line "
                 << SMgr.getLogicalLineNumber(S->getLocStart()) << ".\n";

              break;
            }

            case Stmt::CaseStmtClass: {

              os << "Control jumps to 'case ";

              Expr* CondE = cast<SwitchStmt>(T)->getCond();
              unsigned bits = Ctx.getTypeSize(CondE->getType());

              llvm::APSInt V1(bits, false);

              CaseStmt* Case = cast<CaseStmt>(S);

              if (!Case->getLHS()->isIntegerConstantExpr(V1, Ctx, 0, true)) {
                assert (false &&
                        "Case condition must evaluate to an integer constant.");
                continue;
              }

              os << V1.toString();

              // Get the RHS of the case, if it exists.

              if (Expr* E = Case->getRHS()) {

                llvm::APSInt V2(bits, false);

                if (!E->isIntegerConstantExpr(V2, Ctx, 0, true)) {
                  assert (false &&
                  "Case condition (RHS) must evaluate to an integer constant.");
                  continue;
                }

                os << " .. " << V2.toString();
              }

              os << ":'  at line "
                << SMgr.getLogicalLineNumber(S->getLocStart()) << ".\n";

              break;

            }
          }

          PD.push_front(new PathDiagnosticPiece(L, os.str()));
          break;
        }

        case Stmt::ConditionalOperatorClass: {

          std::ostringstream os;
          os << "'?' condition evaluates to ";

          if (*(Src->succ_begin()+1) == Dst)
            os << "false.";
          else
            os << "true.";

          PD.push_front(new PathDiagnosticPiece(L, os.str()));

          break;
        }

        case Stmt::DoStmtClass:  {

          if (*(Src->succ_begin()) == Dst) {

            std::ostringstream os;

            os << "Loop condition is true. Execution continues on line "
               << SMgr.getLogicalLineNumber(GetStmt(Dst)->getLocStart()) << '.';

            PD.push_front(new PathDiagnosticPiece(L, os.str()));
          }
          else
            PD.push_front(new PathDiagnosticPiece(L,
                              "Loop condition is false.  Exiting loop."));

          break;
        }

        case Stmt::WhileStmtClass:
        case Stmt::ForStmtClass: {

          if (*(Src->succ_begin()+1) == Dst) {

            std::ostringstream os;

            os << "Loop condition is false. Execution continues on line "
               << SMgr.getLogicalLineNumber(GetStmt(Dst)->getLocStart()) << '.';

            PD.push_front(new PathDiagnosticPiece(L, os.str()));
          }
          else
            PD.push_front(new PathDiagnosticPiece(L,
                            "Loop condition is true.  Entering loop body."));

          break;
        }

        case Stmt::IfStmtClass: {

          if (*(Src->succ_begin()+1) == Dst)
            PD.push_front(new PathDiagnosticPiece(L, "Taking false branch."));
          else
            PD.push_front(new PathDiagnosticPiece(L, "Taking true branch."));

          break;
        }
      }
    }
    else
      if (PathDiagnosticPiece* piece = R.VisitNode(N, NextNode, *GTrim, Ctx))
        PD.push_front(piece);
  }
}

bool BugReporter::IsCached(ExplodedNode<ValueState>* N) {

  // HACK: Cache the location of the error.  Don't emit the same
  // warning for the same error type that occurs at the same program
  // location but along a different path.

  void* p = N->getLocation().getRawData();

  if (CachedErrors.count(p))
    return true;

  CachedErrors.insert(p);

  return false;
}

void BugReporter::EmitWarning(BugReport& R) {

  if (IsCached(R.getEndNode()))
    return;

  PathDiagnostic D(R.getName());
  GeneratePathDiagnostic(D, R);

  // Emit a full diagnostic for the path if we have a PathDiagnosticClient.

  if (PD && !D.empty()) {
    PD->HandlePathDiagnostic(D);
    return;
  }

  // We don't have a PathDiagnosticClient, but we can still emit a single
  // line diagnostic.  Determine the location.

  FullSourceLoc L = D.empty() ? R.getLocation(Ctx.getSourceManager())
                               : D.back()->getLocation();

  // Determine the range.

  const SourceRange *Beg, *End;

  if (!D.empty()) {
    Beg = D.back()->ranges_begin();
    End = D.back()->ranges_end();
  }
  else
    R.getRanges(Beg, End);

  // Compute the message.

  std::ostringstream os;
  os << "[CHECKER] ";

  if (D.empty())
    os << R.getDescription();
  else
    os << D.back()->getString();

  unsigned ErrorDiag = Diag.getCustomDiagID(Diagnostic::Warning,
                                            os.str().c_str());

  Diag.Report(L, ErrorDiag, NULL, 0, Beg, End - Beg);
}
<commit_msg>Added null check.<commit_after>// BugReporter.cpp - Generate PathDiagnostics for Bugs ------------*- C++ -*--//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
//  This file defines BugReporter, a utility class for generating
//  PathDiagnostics for analyses based on GRSimpleVals.
//
//===----------------------------------------------------------------------===//

#include "clang/Analysis/PathSensitive/BugReporter.h"
#include "clang/Analysis/PathSensitive/GRExprEngine.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/CFG.h"
#include "clang/AST/Expr.h"
#include "clang/Analysis/ProgramPoint.h"
#include "clang/Analysis/PathDiagnostic.h"
#include <sstream>

using namespace clang;

BugReporter::~BugReporter() {}
BugType::~BugType() {}
BugReport::~BugReport() {}
RangedBugReport::~RangedBugReport() {}

ExplodedGraph<ValueState>& BugReporter::getGraph() { return Eng.getGraph(); }

static inline Stmt* GetStmt(const ProgramPoint& P) {
  if (const PostStmt* PS = dyn_cast<PostStmt>(&P)) {
    return PS->getStmt();
  }
  else if (const BlockEdge* BE = dyn_cast<BlockEdge>(&P)) {
    return BE->getSrc()->getTerminator();
  }
  else if (const BlockEntrance* BE = dyn_cast<BlockEntrance>(&P)) {
    return BE->getFirstStmt();
  }

  assert (false && "Unsupported ProgramPoint.");
  return NULL;
}

static inline Stmt* GetStmt(const CFGBlock* B) {
  assert (!B->empty());
  return (*B)[0];
}

Stmt* BugReport::getStmt() const {
  return N ? GetStmt(N->getLocation()) : NULL;
}

static inline ExplodedNode<ValueState>*
GetNextNode(ExplodedNode<ValueState>* N) {
  return N->pred_empty() ? NULL : *(N->pred_begin());
}

static Stmt* GetLastStmt(ExplodedNode<ValueState>* N) {
  assert (isa<BlockEntrance>(N->getLocation()));

  for (N = GetNextNode(N); N; N = GetNextNode(N)) {

    ProgramPoint P = N->getLocation();

    if (PostStmt* PS = dyn_cast<PostStmt>(&P))
      return PS->getStmt();
  }

  return NULL;
}

PathDiagnosticPiece*
BugReport::getEndPath(BugReporter& BR,
                      ExplodedNode<ValueState>* EndPathNode) const {

  ProgramPoint ProgP = EndPathNode->getLocation();
  Stmt *S = NULL;

  if (BlockEntrance* BE = dyn_cast<BlockEntrance>(&ProgP))
    if (BE->getBlock() == &BR.getCFG().getExit())
      S = GetLastStmt(EndPathNode);
  if (!S)
    S = GetStmt(ProgP);

  if (!S)
    return NULL;

  FullSourceLoc L(S->getLocStart(), BR.getContext().getSourceManager());

  PathDiagnosticPiece* P =
    new PathDiagnosticPiece(L, getDescription());

  const SourceRange *Beg, *End;
  getRanges(Beg, End);

  if (Beg == End) {
    if (Expr* E = dyn_cast<Expr>(S))
      P->addRange(E->getSourceRange());
  }
  else {
    assert (Beg < End);
    for (; Beg != End; ++Beg)
      P->addRange(*Beg);
  }

  return P;
}

void BugReport::getRanges(const SourceRange*& beg,
                          const SourceRange*& end) const {
  beg = NULL;
  end = NULL;
}

FullSourceLoc BugReport::getLocation(SourceManager& Mgr) {

  if (!N)
    return FullSourceLoc();

  Stmt* S = GetStmt(N->getLocation());

  if (!S)
    return FullSourceLoc();

  return FullSourceLoc(S->getLocStart(), Mgr);
}

PathDiagnosticPiece* BugReport::VisitNode(ExplodedNode<ValueState>* N,
                                          ExplodedNode<ValueState>* PrevN,
                                          ExplodedGraph<ValueState>& G,
                                          ASTContext& Ctx) {
  return NULL;
}

void BugReporter::GeneratePathDiagnostic(PathDiagnostic& PD,
                                         BugReport& R) {

  ExplodedNode<ValueState>* N = R.getEndNode();

  if (!N)
    return;

  llvm::OwningPtr<ExplodedGraph<ValueState> > GTrim(getGraph().Trim(&N, &N+1));

  // Find the sink in the trimmed graph.
  // FIXME: Should we eventually have a sink iterator?

  ExplodedNode<ValueState>* NewN = 0;

  for (ExplodedGraph<ValueState>::node_iterator
       I = GTrim->nodes_begin(), E = GTrim->nodes_end(); I != E; ++I) {

    if (I->isSink()) {
      NewN = &*I;
      break;
    }
  }

  assert (NewN);
  assert (NewN->getLocation() == N->getLocation());

  N = NewN;

  if (PathDiagnosticPiece* Piece = R.getEndPath(*this, N))
    PD.push_back(Piece);
  else
    return;

  ExplodedNode<ValueState>* NextNode = N->pred_empty()
                                       ? NULL : *(N->pred_begin());

  SourceManager& SMgr = Ctx.getSourceManager();

  while (NextNode) {

    ExplodedNode<ValueState>* LastNode = N;
    N = NextNode;
    NextNode = GetNextNode(N);

    ProgramPoint P = N->getLocation();

    if (const BlockEdge* BE = dyn_cast<BlockEdge>(&P)) {

      CFGBlock* Src = BE->getSrc();
      CFGBlock* Dst = BE->getDst();

      Stmt* T = Src->getTerminator();

      if (!T)
        continue;

      FullSourceLoc L(T->getLocStart(), SMgr);

      switch (T->getStmtClass()) {
        default:
          break;

        case Stmt::GotoStmtClass:
        case Stmt::IndirectGotoStmtClass: {

          Stmt* S = GetStmt(LastNode->getLocation());

          if (!S)
            continue;

          std::ostringstream os;

          os << "Control jumps to line "
             << SMgr.getLogicalLineNumber(S->getLocStart()) << ".\n";

          PD.push_front(new PathDiagnosticPiece(L, os.str()));
          break;
        }

        case Stmt::SwitchStmtClass: {

          // Figure out what case arm we took.

          Stmt* S = Dst->getLabel();

          if (!S)
            continue;

          std::ostringstream os;

          switch (S->getStmtClass()) {
            default:
              continue;

            case Stmt::DefaultStmtClass: {

              os << "Control jumps to the 'default' case at line "
                 << SMgr.getLogicalLineNumber(S->getLocStart()) << ".\n";

              break;
            }

            case Stmt::CaseStmtClass: {

              os << "Control jumps to 'case ";

              Expr* CondE = cast<SwitchStmt>(T)->getCond();
              unsigned bits = Ctx.getTypeSize(CondE->getType());

              llvm::APSInt V1(bits, false);

              CaseStmt* Case = cast<CaseStmt>(S);

              if (!Case->getLHS()->isIntegerConstantExpr(V1, Ctx, 0, true)) {
                assert (false &&
                        "Case condition must evaluate to an integer constant.");
                continue;
              }

              os << V1.toString();

              // Get the RHS of the case, if it exists.

              if (Expr* E = Case->getRHS()) {

                llvm::APSInt V2(bits, false);

                if (!E->isIntegerConstantExpr(V2, Ctx, 0, true)) {
                  assert (false &&
                  "Case condition (RHS) must evaluate to an integer constant.");
                  continue;
                }

                os << " .. " << V2.toString();
              }

              os << ":'  at line "
                << SMgr.getLogicalLineNumber(S->getLocStart()) << ".\n";

              break;

            }
          }

          PD.push_front(new PathDiagnosticPiece(L, os.str()));
          break;
        }

        case Stmt::ConditionalOperatorClass: {

          std::ostringstream os;
          os << "'?' condition evaluates to ";

          if (*(Src->succ_begin()+1) == Dst)
            os << "false.";
          else
            os << "true.";

          PD.push_front(new PathDiagnosticPiece(L, os.str()));

          break;
        }

        case Stmt::DoStmtClass:  {

          if (*(Src->succ_begin()) == Dst) {

            std::ostringstream os;

            os << "Loop condition is true. Execution continues on line "
               << SMgr.getLogicalLineNumber(GetStmt(Dst)->getLocStart()) << '.';

            PD.push_front(new PathDiagnosticPiece(L, os.str()));
          }
          else
            PD.push_front(new PathDiagnosticPiece(L,
                              "Loop condition is false.  Exiting loop."));

          break;
        }

        case Stmt::WhileStmtClass:
        case Stmt::ForStmtClass: {

          if (*(Src->succ_begin()+1) == Dst) {

            std::ostringstream os;

            os << "Loop condition is false. Execution continues on line "
               << SMgr.getLogicalLineNumber(GetStmt(Dst)->getLocStart()) << '.';

            PD.push_front(new PathDiagnosticPiece(L, os.str()));
          }
          else
            PD.push_front(new PathDiagnosticPiece(L,
                            "Loop condition is true.  Entering loop body."));

          break;
        }

        case Stmt::IfStmtClass: {

          if (*(Src->succ_begin()+1) == Dst)
            PD.push_front(new PathDiagnosticPiece(L, "Taking false branch."));
          else
            PD.push_front(new PathDiagnosticPiece(L, "Taking true branch."));

          break;
        }
      }
    }
    else
      if (PathDiagnosticPiece* piece = R.VisitNode(N, NextNode, *GTrim, Ctx))
        PD.push_front(piece);
  }
}

bool BugReporter::IsCached(ExplodedNode<ValueState>* N) {

  if (!N)
    return false;

  // HACK: Cache the location of the error.  Don't emit the same
  // warning for the same error type that occurs at the same program
  // location but along a different path.

  void* p = N->getLocation().getRawData();

  if (CachedErrors.count(p))
    return true;

  CachedErrors.insert(p);

  return false;
}

void BugReporter::EmitWarning(BugReport& R) {

  if (IsCached(R.getEndNode()))
    return;

  PathDiagnostic D(R.getName());
  GeneratePathDiagnostic(D, R);

  // Emit a full diagnostic for the path if we have a PathDiagnosticClient.

  if (PD && !D.empty()) {
    PD->HandlePathDiagnostic(D);
    return;
  }

  // We don't have a PathDiagnosticClient, but we can still emit a single
  // line diagnostic.  Determine the location.

  FullSourceLoc L = D.empty() ? R.getLocation(Ctx.getSourceManager())
                               : D.back()->getLocation();

  // Determine the range.

  const SourceRange *Beg, *End;

  if (!D.empty()) {
    Beg = D.back()->ranges_begin();
    End = D.back()->ranges_end();
  }
  else
    R.getRanges(Beg, End);

  // Compute the message.

  std::ostringstream os;
  os << "[CHECKER] ";

  if (D.empty())
    os << R.getDescription();
  else
    os << D.back()->getString();

  unsigned ErrorDiag = Diag.getCustomDiagID(Diagnostic::Warning,
                                            os.str().c_str());

  Diag.Report(L, ErrorDiag, NULL, 0, Beg, End - Beg);
}
<|endoftext|>


<commit_before>/*
 * Copyright 2006-2008 The FLWOR Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "stdafx.h"

#include <cerrno>
#include <cstdlib>

#include "ascii_util.h"
#include "cxx_util.h"
#include "string_util.h"
#include "zorbatypes/zstring.h"

#ifdef WIN32
namespace std {

  // Windows doesn't have these functions -- add them ourselves.

  static float strtof( char const *s, char **end ) {
    double const result = std::strtod( s, end );
    if ( !errno ) {
      if ( result < std::numeric_limits<float>::min() ||
           result > std::numeric_limits<float>::max() )
        errno = ERANGE;
    }
    return static_cast<float>( result );
  }

  inline long long strtoll( char const *s, char **end, int base ) {
    return ::_strtoi64( s, end, base );
  }

  inline unsigned long long strtoull( char const *s, char **end, int base ) {
    return ::_strtoui64( s, end, base );
  }

} // namespace std
#endif /* WIN32 */

using namespace std;

namespace zorba {
namespace ztd {

///////////////////////////////////////////////////////////////////////////////

static void check_errno( char const *buf, char const *end ) {
  if ( errno == ERANGE ) {
    zstring const s( buf, end );
    throw std::range_error(
      BUILD_STRING( '"', s, "\": number too big/small" )
    );
  }
}

static void check_parse_number( char const *buf, char const *end,
                                bool check_trailing_chars ) {
  if ( end == buf )
    throw std::invalid_argument( BUILD_STRING( '"', buf, "\": no digits" ) );
  if ( check_trailing_chars )
    for ( ; *end; ++end )               // remaining characters, if any, ...
      if ( !ascii::is_space( *end ) )   // ... may only be whitespace
        throw std::invalid_argument(
          BUILD_STRING( '"', *end, "\": invalid character" )
        );
}

#define ATON_PREAMBLE()           \
  bool check_trailing_chars;      \
  char const *pc;                 \
  if ( end ) {                    \
    check_trailing_chars = false; \
  } else {                        \
    end = &pc;                    \
    check_trailing_chars = true;  \
  }                               \
  errno = 0

///////////////////////////////////////////////////////////////////////////////

double atod( char const *buf, char const **end ) {
  ATON_PREAMBLE();
  double const result = std::strtod( buf, (char**)end );
  check_parse_number( buf, *end, check_trailing_chars );
  return result;
}

float atof( char const *buf, char const **end ) {
  ATON_PREAMBLE();
  float const result = std::strtof( buf, (char**)end );
  check_parse_number( buf, *end, check_trailing_chars );
  return result;
}

long long atoll( char const *buf, char const **end ) {
  ATON_PREAMBLE();
  long long const result = std::strtoll( buf, (char**)end, 10 );
  check_errno( buf, *end );
  check_parse_number( buf, *end, check_trailing_chars );
  return result;
}

unsigned long long atoull( char const *buf, char const **end ) {
  ATON_PREAMBLE();
  //
  // We have to check for '-' ourselves since strtoull(3) allows it (oddly).
  //
  buf = ascii::trim_start_whitespace( buf );
  bool const minus = *buf == '-';

  unsigned long long const result = std::strtoull( buf, (char**)end, 10 );
  check_errno( buf, *end );
  check_parse_number( buf, *end, check_trailing_chars );

  if ( minus && result ) {
    //
    // Throw an exception only if there was a '-' and the result is non-zero.
    // Hence, this allows "-0" and treats it as "0".
    //
    throw std::invalid_argument(
      "'-': invalid character for unsigned integer"
    );
  }
  return result;
}

char* itoa( long long n, char *buf ) {
  //
  // This implementation is much faster than using sprintf(3).
  //
  char *s = buf;
  long long n_prev;
  do {
    n_prev = n;
    n /= 10;
    *s++ = "9876543210123456789" [ 9 + n_prev - n * 10 ];
  } while ( n );

  if ( n_prev < 0 ) *s++ = '-';
  *s = '\0';

  for ( char *t = buf; t < s; ++t ) {
    char const c = *--s; *s = *t; *t = c;
  }
  return buf;
}

char* itoa( unsigned long long n, char *buf ) {
  char *s = buf;
  unsigned long long n_prev;
  do {
    n_prev = n;
    n /= 10;
    *s++ = "0123456789" [ n_prev - n * 10 ];
  } while ( n );

  *s = '\0';

  for ( char *t = buf; t < s; ++t ) {
    char const c = *--s; *s = *t; *t = c;
  }
  return buf;
}

///////////////////////////////////////////////////////////////////////////////

} // namespace ztd
} // namespace zorba
/* vim:set et sw=2 ts=2: */
<commit_msg>Clean-up.<commit_after>/*
 * Copyright 2006-2008 The FLWOR Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "stdafx.h"

#include <cerrno>
#include <cstdlib>

#include "ascii_util.h"
#include "cxx_util.h"
#include "string_util.h"
#include "zorbatypes/zstring.h"

#ifdef WIN32
namespace std {

  // Windows doesn't have these functions -- add them ourselves.

  static float strtof( char const *s, char **end ) {
    double const result = std::strtod( s, end );
    if ( !errno ) {
      if ( result < std::numeric_limits<float>::min() ||
           result > std::numeric_limits<float>::max() )
        errno = ERANGE;
    }
    return static_cast<float>( result );
  }

  inline long long strtoll( char const *s, char **end, int base ) {
    return ::_strtoi64( s, end, base );
  }

  inline unsigned long long strtoull( char const *s, char **end, int base ) {
    return ::_strtoui64( s, end, base );
  }

} // namespace std
#endif /* WIN32 */

using namespace std;

namespace zorba {
namespace ztd {

///////////////////////////////////////////////////////////////////////////////

static void no_digits( char const *buf ) {
  throw std::invalid_argument( BUILD_STRING( '"', buf, "\": no digits" ) );
}

static void too_big_or_small( char const *buf, char const *end ) {
  zstring const s( buf, end );
  throw std::range_error( BUILD_STRING( '"', s, "\": number too big/small" ) );
}

inline void check_errno( char const *buf, char const *end ) {
  if ( errno == ERANGE )
    too_big_or_small( buf, end );
}

static void check_trailing_chars_impl( char const *end ) {
  for ( ; *end; ++end )                 // remaining characters, if any, ...
    if ( !ascii::is_space( *end ) )     // ... may only be whitespace
      throw std::invalid_argument(
        BUILD_STRING( '\'', *end, "': invalid character" )
      );
}

inline void check_parse_number( char const *buf, char const *end,
                                bool check_trailing_chars ) {
  if ( end == buf )
    no_digits( buf );
  if ( check_trailing_chars )
    check_trailing_chars_impl( end );
}

class aton_context {
public:
  aton_context( char const **&end ) {
    if ( end ) {
      check_trailing_chars_ = false;
    } else {
      end = &end_;
      check_trailing_chars_ = true;
    }
    errno = 0;
  }
  bool check_trailing_chars() const {
    return check_trailing_chars_;
  }
private:
  bool check_trailing_chars_;
  char const *end_;
};

///////////////////////////////////////////////////////////////////////////////

double atod( char const *buf, char const **end ) {
  aton_context const ctx( end );
  double const result = std::strtod( buf, (char**)end );
  check_parse_number( buf, *end, ctx.check_trailing_chars() );
  return result;
}

float atof( char const *buf, char const **end ) {
  aton_context const ctx( end );
  float const result = std::strtof( buf, (char**)end );
  check_parse_number( buf, *end, ctx.check_trailing_chars() );
  return result;
}

long long atoll( char const *buf, char const **end ) {
  aton_context const ctx( end );
  long long const result = std::strtoll( buf, (char**)end, 10 );
  check_errno( buf, *end );
  check_parse_number( buf, *end, ctx.check_trailing_chars() );
  return result;
}

unsigned long long atoull( char const *buf, char const **end ) {
  aton_context const ctx( end );
  //
  // We have to check for '-' ourselves since strtoull(3) allows it (oddly).
  //
  buf = ascii::trim_start_whitespace( buf );
  bool const minus = *buf == '-';

  unsigned long long const result = std::strtoull( buf, (char**)end, 10 );
  check_errno( buf, *end );
  check_parse_number( buf, *end, ctx.check_trailing_chars() );

  if ( minus && result ) {
    //
    // Throw an exception only if there was a '-' and the result is non-zero.
    // Hence, this allows "-0" and treats it as "0".
    //
    throw std::invalid_argument(
      "'-': invalid character for unsigned integer"
    );
  }
  return result;
}

char* itoa( long long n, char *buf ) {
  //
  // This implementation is much faster than using sprintf(3).
  //
  char *s = buf;
  long long n_prev;
  do {
    n_prev = n;
    n /= 10;
    *s++ = "9876543210123456789" [ 9 + n_prev - n * 10 ];
  } while ( n );

  if ( n_prev < 0 ) *s++ = '-';
  *s = '\0';

  for ( char *t = buf; t < s; ++t ) {
    char const c = *--s; *s = *t; *t = c;
  }
  return buf;
}

char* itoa( unsigned long long n, char *buf ) {
  char *s = buf;
  unsigned long long n_prev;
  do {
    n_prev = n;
    n /= 10;
    *s++ = "0123456789" [ n_prev - n * 10 ];
  } while ( n );

  *s = '\0';

  for ( char *t = buf; t < s; ++t ) {
    char const c = *--s; *s = *t; *t = c;
  }
  return buf;
}

///////////////////////////////////////////////////////////////////////////////

} // namespace ztd
} // namespace zorba
/* vim:set et sw=2 ts=2: */
<|endoftext|>


<commit_before>/*
* ----------------------------------------------------------------------------
* “THE COFFEEWARE LICENSE” (Revision 1):
* <ihsan@kehribar.me> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a coffee in return.
* -----------------------------------------------------------------------------
* This library is based on this library:
*   https://github.com/aaronds/arduino-nrf24l01
* Which is based on this library:
*   http://www.tinkerer.eu/AVRLib/nRF24L01
* -----------------------------------------------------------------------------
*/
#include "nrf24.hpp"

uint8_t payload_len;

// FIXME: correct pin definitions
typedef xpcc::stm32::GpioE6 Ce;
typedef xpcc::stm32::GpioA4 Csn;
typedef xpcc::stm32::GpioA5 Sck;
typedef xpcc::stm32::GpioA7 Mosi;
typedef xpcc::stm32::GpioA6 Miso;

/* init the hardware pins */
void nrf24_init()
{
/*    - Set MISO pin input
 *    - Set MOSI pin output
 *    - Set SCK pin output
 *    - Set CSN pin output
 *    - Set CE pin output     */
    Miso::setInput();
    Mosi::setOutput(xpcc::Gpio::Low);
    Sck::setOutput(xpcc::Gpio::Low);
    Csn::setOutput(xpcc::Gpio::Low);
    Ce::setOutput(xpcc::Gpio::Low);

    Ce::reset();
    Csn::set();
}

/* configure the module */
void nrf24_config(uint8_t channel, uint8_t pay_length)
{
    /* Use static payload length ... */
    payload_len = pay_length;

    // Set RF channel
    nrf24_configRegister(RF_CH,channel);

    // Set length of incoming payload
    nrf24_configRegister(RX_PW_P0, 0x00); // Auto-ACK pipe ...
    nrf24_configRegister(RX_PW_P1, payload_len); // Data payload pipe
    nrf24_configRegister(RX_PW_P2, 0x00); // Pipe not used
    nrf24_configRegister(RX_PW_P3, 0x00); // Pipe not used
    nrf24_configRegister(RX_PW_P4, 0x00); // Pipe not used
    nrf24_configRegister(RX_PW_P5, 0x00); // Pipe not used

    // 1 Mbps, TX gain: 0dbm
    nrf24_configRegister(RF_SETUP, (0<<RF_DR)|((0x03)<<RF_PWR));

    // CRC enable, 1 byte CRC length
    nrf24_configRegister(CONFIG,nrf24_CONFIG);

    // Auto Acknowledgment
    nrf24_configRegister(EN_AA,(1<<ENAA_P0)|(1<<ENAA_P1)|(0<<ENAA_P2)|(0<<ENAA_P3)|(0<<ENAA_P4)|(0<<ENAA_P5));

    // Enable RX addresses
    nrf24_configRegister(EN_RXADDR,(1<<ERX_P0)|(1<<ERX_P1)|(0<<ERX_P2)|(0<<ERX_P3)|(0<<ERX_P4)|(0<<ERX_P5));

    // Auto retransmit delay: 1000 us and Up to 15 retransmit trials
    nrf24_configRegister(SETUP_RETR,(0x04<<ARD)|(0x0F<<ARC));

    // Dynamic length configurations: No dynamic length
    nrf24_configRegister(DYNPD,(0<<DPL_P0)|(0<<DPL_P1)|(0<<DPL_P2)|(0<<DPL_P3)|(0<<DPL_P4)|(0<<DPL_P5));

    // Start listening
    nrf24_powerUpRx();
}

/* Set the RX address */
void nrf24_rx_address(const uint8_t * adr)
{
    Ce::reset();
    nrf24_writeRegister(RX_ADDR_P1,adr,nrf24_ADDR_LEN);
    Ce::set();
}

/* Returns the payload length */
uint8_t nrf24_payload_length()
{
    return payload_len;
}

/* Set the TX address */
void nrf24_tx_address(const uint8_t* adr)
{
    /* RX_ADDR_P0 must be set to the sending addr for auto ack to work. */
    nrf24_writeRegister(RX_ADDR_P0,adr,nrf24_ADDR_LEN);
    nrf24_writeRegister(TX_ADDR,adr,nrf24_ADDR_LEN);
}

/* Checks if data is available for reading */
/* Returns 1 if data is ready ... */
uint8_t nrf24_dataReady()
{
    // See note in getData() function - just checking RX_DR isn't good enough
    uint8_t status = nrf24_getStatus();

    // We can short circuit on RX_DR, but if it's not set, we still need
    // to check the FIFO for any pending packets
    if ( status & (1 << RX_DR) )
    {
        return 1;
    }

    return !nrf24_rxFifoEmpty();;
}

/* Checks if receive FIFO is empty or not */
uint8_t nrf24_rxFifoEmpty()
{
    uint8_t fifoStatus;

    nrf24_readRegister(FIFO_STATUS,&fifoStatus,1);

    return (fifoStatus & (1 << RX_EMPTY));
}

/* Returns the length of data waiting in the RX fifo */
uint8_t nrf24_payloadLength()
{
    uint8_t status;
    Csn::reset();
    spi_transfer(R_RX_PL_WID);
    status = spi_transfer(0x00);
    Csn::set();
    return status;
}

/* Reads payload bytes into data array */
void nrf24_getData(uint8_t* data)
{
    /* Pull down chip select */
    Csn::reset();

    /* Send cmd to read rx payload */
    spi_transfer( R_RX_PAYLOAD );

    /* Read payload */
    nrf24_transferSync(data,data,payload_len);

    /* Pull up chip select */
    Csn::set();

    /* Reset status register */
    nrf24_configRegister(STATUS,(1<<RX_DR));
}

/* Returns the number of retransmissions occured for the last message */
uint8_t nrf24_retransmissionCount()
{
    uint8_t rv;
    nrf24_readRegister(OBSERVE_TX,&rv,1);
    rv = rv & 0x0F;
    return rv;
}

// Sends a data package to the default address. Be sure to send the correct
// amount of bytes as configured as payload on the receiver.
void nrf24_send(uint8_t* value)
{
    /* Go to Standby-I first */
    Ce::reset();

    /* Set to transmitter mode , Power up if needed */
    nrf24_powerUpTx();

    /* Do we really need to flush TX fifo each time ? */
    #if 1
        /* Pull down chip select */
        Csn::reset();

        /* Write cmd to flush transmit FIFO */
        spi_transfer(FLUSH_TX);

        /* Pull up chip select */
        Csn::set();
    #endif

    /* Pull down chip select */
    Csn::reset();

    /* Write cmd to write payload */
    spi_transfer(W_TX_PAYLOAD);

    /* Write payload */
    nrf24_transmitSync(value,payload_len);

    /* Pull up chip select */
    Csn::set();

    /* Start the transmission */
    Ce::set();
}

uint8_t nrf24_isSending()
{
    uint8_t status;

    /* read the current status */
    status = nrf24_getStatus();

    /* if sending successful (TX_DS) or max retries exceded (MAX_RT). */
    if((status & ((1 << TX_DS)  | (1 << MAX_RT))))
    {
        return 0; /* false */
    }

    return 1; /* true */

}

uint8_t nrf24_getStatus()
{
    uint8_t rv;
    Csn::reset();
    rv = spi_transfer(NOP);
    Csn::set();
    return rv;
}

uint8_t nrf24_lastMessageStatus()
{
    uint8_t rv;

    rv = nrf24_getStatus();

    /* Transmission went OK */
    if((rv & ((1 << TX_DS))))
    {
        return NRF24_TRANSMISSON_OK;
    }
    /* Maximum retransmission count is reached */
    /* Last message probably went missing ... */
    else if((rv & ((1 << MAX_RT))))
    {
        return NRF24_MESSAGE_LOST;
    }
    /* Probably still sending ... */
    else
    {
        return 0xFF;
    }
}

void nrf24_powerUpRx()
{
    Csn::reset();
    spi_transfer(FLUSH_RX);
    Csn::set();

    nrf24_configRegister(STATUS,(1<<RX_DR)|(1<<TX_DS)|(1<<MAX_RT));

    Ce::reset();
    nrf24_configRegister(CONFIG,nrf24_CONFIG|((1<<PWR_UP)|(1<<PRIM_RX)));
    Ce::set();
}

void nrf24_powerUpTx()
{
    nrf24_configRegister(STATUS,(1<<RX_DR)|(1<<TX_DS)|(1<<MAX_RT));

    nrf24_configRegister(CONFIG,nrf24_CONFIG|((1<<PWR_UP)|(0<<PRIM_RX)));
}

void nrf24_powerDown()
{
    Ce::reset();
    nrf24_configRegister(CONFIG,nrf24_CONFIG);
}

/* software spi routine */
uint8_t spi_transfer(uint8_t tx)
{
    uint8_t i = 0;
    uint8_t rx = 0;

    Sck::reset();

    for(i=0;i<8;i++)
    {

        if(tx & (1<<(7-i)))
        {
            Mosi::set();
        }
        else
        {
            Mosi::reset();
        }

        Sck::set();

        rx = rx << 1;
        if(Miso::read())
        {
            rx |= 0x01;
        }

        Sck::reset();

    }

    return rx;
}

/* send and receive multiple bytes over SPI */
void nrf24_transferSync(const uint8_t* dataout,uint8_t* datain,uint8_t len)
{
    uint8_t i;

    for(i=0;i<len;i++)
    {
        datain[i] = spi_transfer(dataout[i]);
    }

}

/* send multiple bytes over SPI */
void nrf24_transmitSync(const uint8_t* dataout,uint8_t len)
{
    uint8_t i;

    for(i=0;i<len;i++)
    {
        spi_transfer(dataout[i]);
    }

}

/* Clocks only one byte into the given nrf24 register */
void nrf24_configRegister(uint8_t reg, uint8_t value)
{
    Csn::reset();
    spi_transfer(W_REGISTER | (REGISTER_MASK & reg));
    spi_transfer(value);
    Csn::set();
}

/* Read single register from nrf24 */
void nrf24_readRegister(uint8_t reg, uint8_t* value, uint8_t len)
{
    Csn::reset();
    spi_transfer(R_REGISTER | (REGISTER_MASK & reg));
    nrf24_transferSync(value,value,len);
    Csn::set();
}

/* Write to a single register of nrf24 */
void nrf24_writeRegister(uint8_t reg, const uint8_t* value, uint8_t len)
{
    Csn::reset();
    spi_transfer(W_REGISTER | (REGISTER_MASK & reg));
    nrf24_transmitSync(value,len);
    Csn::set();
}
<commit_msg>add delays everywhere<commit_after>/*
* ----------------------------------------------------------------------------
* “THE COFFEEWARE LICENSE” (Revision 1):
* <ihsan@kehribar.me> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a coffee in return.
* -----------------------------------------------------------------------------
* This library is based on this library:
*   https://github.com/aaronds/arduino-nrf24l01
* Which is based on this library:
*   http://www.tinkerer.eu/AVRLib/nRF24L01
* -----------------------------------------------------------------------------
*/
#include "nrf24.hpp"

uint8_t payload_len;

// FIXME: correct pin definitions
typedef xpcc::stm32::GpioE6 Ce;
typedef xpcc::stm32::GpioA4 Csn;
typedef xpcc::stm32::GpioA5 Sck;
typedef xpcc::stm32::GpioA7 Mosi;
typedef xpcc::stm32::GpioA6 Miso;

/* init the hardware pins */
void nrf24_init()
{
/*    - Set MISO pin input
 *    - Set MOSI pin output
 *    - Set SCK pin output
 *    - Set CSN pin output
 *    - Set CE pin output     */
    Miso::setInput();
    Mosi::setOutput(xpcc::Gpio::Low);
    Sck::setOutput(xpcc::Gpio::Low);
    Csn::setOutput(xpcc::Gpio::Low);
    Ce::setOutput(xpcc::Gpio::Low);

    Ce::reset();
    Csn::set();
}

/* configure the module */
void nrf24_config(uint8_t channel, uint8_t pay_length)
{
    /* Use static payload length ... */
    payload_len = pay_length;

    // Set RF channel
    nrf24_configRegister(RF_CH,channel);

    // Set length of incoming payload
    nrf24_configRegister(RX_PW_P0, 0x00); // Auto-ACK pipe ...
    nrf24_configRegister(RX_PW_P1, payload_len); // Data payload pipe
    nrf24_configRegister(RX_PW_P2, 0x00); // Pipe not used
    nrf24_configRegister(RX_PW_P3, 0x00); // Pipe not used
    nrf24_configRegister(RX_PW_P4, 0x00); // Pipe not used
    nrf24_configRegister(RX_PW_P5, 0x00); // Pipe not used

    // 1 Mbps, TX gain: 0dbm
    nrf24_configRegister(RF_SETUP, (0<<RF_DR)|((0x03)<<RF_PWR));

    // CRC enable, 1 byte CRC length
    nrf24_configRegister(CONFIG,nrf24_CONFIG);

    // Auto Acknowledgment
    nrf24_configRegister(EN_AA,(1<<ENAA_P0)|(1<<ENAA_P1)|(0<<ENAA_P2)|(0<<ENAA_P3)|(0<<ENAA_P4)|(0<<ENAA_P5));

    // Enable RX addresses
    nrf24_configRegister(EN_RXADDR,(1<<ERX_P0)|(1<<ERX_P1)|(0<<ERX_P2)|(0<<ERX_P3)|(0<<ERX_P4)|(0<<ERX_P5));

    // Auto retransmit delay: 1000 us and Up to 15 retransmit trials
    nrf24_configRegister(SETUP_RETR,(0x04<<ARD)|(0x0F<<ARC));

    // Dynamic length configurations: No dynamic length
    nrf24_configRegister(DYNPD,(0<<DPL_P0)|(0<<DPL_P1)|(0<<DPL_P2)|(0<<DPL_P3)|(0<<DPL_P4)|(0<<DPL_P5));

    // Start listening
    nrf24_powerUpRx();
}

/* Set the RX address */
void nrf24_rx_address(const uint8_t * adr)
{
    Ce::reset();
    xpcc::delay_ms(1);
    nrf24_writeRegister(RX_ADDR_P1,adr,nrf24_ADDR_LEN);
    xpcc::delay_ms(1);
    Ce::set();
}

/* Returns the payload length */
uint8_t nrf24_payload_length()
{
    return payload_len;
}

/* Set the TX address */
void nrf24_tx_address(const uint8_t* adr)
{
    /* RX_ADDR_P0 must be set to the sending addr for auto ack to work. */
    nrf24_writeRegister(RX_ADDR_P0,adr,nrf24_ADDR_LEN);
    nrf24_writeRegister(TX_ADDR,adr,nrf24_ADDR_LEN);
}

/* Checks if data is available for reading */
/* Returns 1 if data is ready ... */
uint8_t nrf24_dataReady()
{
    // See note in getData() function - just checking RX_DR isn't good enough
    uint8_t status = nrf24_getStatus();

    // We can short circuit on RX_DR, but if it's not set, we still need
    // to check the FIFO for any pending packets
    if ( status & (1 << RX_DR) )
    {
        return 1;
    }

    return !nrf24_rxFifoEmpty();;
}

/* Checks if receive FIFO is empty or not */
uint8_t nrf24_rxFifoEmpty()
{
    uint8_t fifoStatus;

    nrf24_readRegister(FIFO_STATUS,&fifoStatus,1);

    return (fifoStatus & (1 << RX_EMPTY));
}

/* Returns the length of data waiting in the RX fifo */
uint8_t nrf24_payloadLength()
{
    uint8_t status;
    Csn::reset();
    xpcc::delay_ms(1);
    spi_transfer(R_RX_PL_WID);
    xpcc::delay_ms(1);
    status = spi_transfer(0x00);
    xpcc::delay_ms(1);
    Csn::set();
    xpcc::delay_ms(1);
    return status;
}

/* Reads payload bytes into data array */
void nrf24_getData(uint8_t* data)
{
    /* Pull down chip select */
    Csn::reset();
    xpcc::delay_ms(1);
    /* Send cmd to read rx payload */
    spi_transfer( R_RX_PAYLOAD );
    xpcc::delay_ms(1);
    /* Read payload */
    nrf24_transferSync(data,data,payload_len);
    xpcc::delay_ms(1);
    /* Pull up chip select */
    Csn::set();
    xpcc::delay_ms(1);
    /* Reset status register */
    nrf24_configRegister(STATUS,(1<<RX_DR));
    xpcc::delay_ms(1);
}

/* Returns the number of retransmissions occured for the last message */
uint8_t nrf24_retransmissionCount()
{
    uint8_t rv;
    nrf24_readRegister(OBSERVE_TX,&rv,1);
    rv = rv & 0x0F;
    return rv;
}

// Sends a data package to the default address. Be sure to send the correct
// amount of bytes as configured as payload on the receiver.
void nrf24_send(uint8_t* value)
{
    /* Go to Standby-I first */
    Ce::reset();
    xpcc::delay_ms(1);
    /* Set to transmitter mode , Power up if needed */
    nrf24_powerUpTx();
    xpcc::delay_ms(1);
    /* Do we really need to flush TX fifo each time ? */
    #if 1
        /* Pull down chip select */
        Csn::reset();
        xpcc::delay_ms(1);
        /* Write cmd to flush transmit FIFO */
        spi_transfer(FLUSH_TX);
        xpcc::delay_ms(1);
        /* Pull up chip select */
        Csn::set();
    #endif
        xpcc::delay_ms(1);
    /* Pull down chip select */
    Csn::reset();
    xpcc::delay_ms(1);
    /* Write cmd to write payload */
    spi_transfer(W_TX_PAYLOAD);
    xpcc::delay_ms(1);
    /* Write payload */
    nrf24_transmitSync(value,payload_len);
    xpcc::delay_ms(1);
    /* Pull up chip select */
    Csn::set();
    xpcc::delay_ms(1);
    /* Start the transmission */
    Ce::set();
    xpcc::delay_ms(1);
}

uint8_t nrf24_isSending()
{
    uint8_t status;

    /* read the current status */
    status = nrf24_getStatus();

    /* if sending successful (TX_DS) or max retries exceded (MAX_RT). */
    if((status & ((1 << TX_DS)  | (1 << MAX_RT))))
    {
        return 0; /* false */
    }

    return 1; /* true */

}

uint8_t nrf24_getStatus()
{
    uint8_t rv;
    Csn::reset();
    xpcc::delay_ms(1);
    rv = spi_transfer(NOP);
    xpcc::delay_ms(1);
    Csn::set();
    xpcc::delay_ms(1);
    return rv;
}

uint8_t nrf24_lastMessageStatus()
{
    uint8_t rv;

    rv = nrf24_getStatus();

    /* Transmission went OK */
    if((rv & ((1 << TX_DS))))
    {
        return NRF24_TRANSMISSON_OK;
    }
    /* Maximum retransmission count is reached */
    /* Last message probably went missing ... */
    else if((rv & ((1 << MAX_RT))))
    {
        return NRF24_MESSAGE_LOST;
    }
    /* Probably still sending ... */
    else
    {
        return 0xFF;
    }
}

void nrf24_powerUpRx()
{
    Csn::reset();
    spi_transfer(FLUSH_RX);
    Csn::set();

    nrf24_configRegister(STATUS,(1<<RX_DR)|(1<<TX_DS)|(1<<MAX_RT));

    Ce::reset();
    nrf24_configRegister(CONFIG,nrf24_CONFIG|((1<<PWR_UP)|(1<<PRIM_RX)));
    Ce::set();
}

void nrf24_powerUpTx()
{
    nrf24_configRegister(STATUS,(1<<RX_DR)|(1<<TX_DS)|(1<<MAX_RT));

    nrf24_configRegister(CONFIG,nrf24_CONFIG|((1<<PWR_UP)|(0<<PRIM_RX)));
}

void nrf24_powerDown()
{
    Ce::reset();
    xpcc::delay_ms(1);
    nrf24_configRegister(CONFIG,nrf24_CONFIG);
}

/* software spi routine */
uint8_t spi_transfer(uint8_t tx)
{
    uint8_t i = 0;
    uint8_t rx = 0;
    xpcc::delay_ms(1);
    Sck::reset();

    for(i=0;i<8;i++)
    {

        if(tx & (1<<(7-i)))
        {
            Mosi::set();
        }
        else
        {
            Mosi::reset();
        }
        xpcc::delay_ms(1);
        Sck::set();
        xpcc::delay_ms(1);

        rx = rx << 1;
        if(Miso::read())
        {
            rx |= 0x01;
        }

        Sck::reset();
        xpcc::delay_ms(1);
    }

    return rx;
}

/* send and receive multiple bytes over SPI */
void nrf24_transferSync(const uint8_t* dataout,uint8_t* datain,uint8_t len)
{
    uint8_t i;

    for(i=0;i<len;i++)
    {
        datain[i] = spi_transfer(dataout[i]);
    }

}

/* send multiple bytes over SPI */
void nrf24_transmitSync(const uint8_t* dataout,uint8_t len)
{
    uint8_t i;

    for(i=0;i<len;i++)
    {
        spi_transfer(dataout[i]);
    }

}

/* Clocks only one byte into the given nrf24 register */
void nrf24_configRegister(uint8_t reg, uint8_t value)
{
    Csn::reset();
    xpcc::delay_ms(1);
    spi_transfer(W_REGISTER | (REGISTER_MASK & reg));
    xpcc::delay_ms(1);
    spi_transfer(value);
    xpcc::delay_ms(1);
    Csn::set();
}

/* Read single register from nrf24 */
void nrf24_readRegister(uint8_t reg, uint8_t* value, uint8_t len)
{
    Csn::reset();
    xpcc::delay_ms(1);
    spi_transfer(R_REGISTER | (REGISTER_MASK & reg));
    xpcc::delay_ms(1);
    nrf24_transferSync(value,value,len);
    xpcc::delay_ms(1);
    Csn::set();
}

/* Write to a single register of nrf24 */
void nrf24_writeRegister(uint8_t reg, const uint8_t* value, uint8_t len)
{
    Csn::reset();
    xpcc::delay_ms(1);
    spi_transfer(W_REGISTER | (REGISTER_MASK & reg));
    xpcc::delay_ms(1);
    nrf24_transmitSync(value,len);
    xpcc::delay_ms(1);
    Csn::set();
}
<|endoftext|>


<commit_before>/**
 * \file tests.cpp
 * \author Matt Hammerly
 * \brief This file contains int main() which will run tests as they're written
 */
#include <iostream>
#include <cassert>
#include "Library.h"
#include "Track.h"
#include "Playlist.h"
#include "tests.h"

using std::cout; using std::endl;

/// File location of one track
const std::string track1 = "/home/matt/hdd/dev/cpp/musicmanager/test_data/Gregory And The Hawk/The Boats & Birds EP/01 Boats & Birds.mp3";

/// File location of another track
const std::string track2 = "/home/matt/hdd/dev/cpp/musicmanager/test_data/Beirut/The Flying Club Cup/05 la banlieue.m4a";

/// Title of one playlist
const std::string playlist1 = "test1";

/**
 * \brief Main entry point of program, where tests will be run
 */
int main()
{

    Test_Library_Constructor();

    Test_Library_PrepareDatabase();

    Test_Library_DestroyDatabase();

    Test_Library_AddTrack();

    Test_Library_AddPlaylist();

    Test_Library_RemoveTrack();

    Test_Library_RemovePlaylist();

    Test_Playlist_Constructors();

    Test_Playlist_AppendTrack();

    // So I can poke around manually after running tests
    //CLibrary library;
    //library.PrepareDatabase();

    return 0;
}

/**
 * \brief I mean, there's constructor logic, let's test it.
 */
void Test_Library_Constructor()
{
    cout << "Test_Library_Constructor... ";
    CLibrary library;
    assert(library.GetStatus() == CONNECTION_OK);
    cout << "OK" << endl;
}

/**
 * \brief Ensure all expected database objects are created
 */
void Test_Library_PrepareDatabase()
{
    cout << "Test_Library_PrepareDatabase... ";
    CLibrary library;

    library.PrepareDatabase();

    // We need the unwrapped connection object for arbitrary queries to test
    PGconn *conn = library.GetConnection();

    // Check to see if tables exist
    // Three rows should be returned; one for tracks, one for playlists, one for tracks_playlists
    PGresult* res_tables = PQexec(conn, "SELECT table_name FROM information_schema.tables WHERE table_name IN ('tracks', 'playlists', 'tracks_playlists');");
    assert(PQntuples(res_tables) == 3);
    PQclear(res_tables);

    // Check to see if the tracks_playlists_insert_func procedure exists
    PGresult* res_func = PQexec(conn, "SELECT routine_name FROM information_schema.routines WHERE routine_name = 'tracks_playlists_insert_func';");
    assert(PQntuples(res_func) == 1);
    PQclear(res_func);

    // Check to see if the tracks_playlists_insert_trg trigger exists
    // Two records should be returned; one for ON INSERT, one for ON DELETE
    PGresult* res_trg = PQexec(conn, "SELECT trigger_name FROM information_schema.triggers WHERE trigger_name = 'tracks_playlists_insert_trg';");
    assert(PQntuples(res_trg) == 2);
    PQclear(res_trg);

    // Check to see if the default library playlist was properly created
    PGresult *res_playlist = PQexec(conn, "SELECT id, title, length FROM playlists WHERE id=1");
    assert(PQntuples(res_playlist) == 1);
    PQclear(res_playlist);

    // clean up, I guess
    library.DestroyDatabase();

    cout << "OK" << endl;
}

/**
 * \brief Ensure database objects are deleted when asked
 */
void Test_Library_DestroyDatabase()
{
    cout << "Test_Library_DestroyDatabase... ";
    CLibrary library;

    // Make sure all tables and such exist
    library.PrepareDatabase();

    library.DestroyDatabase();

    // We need the unwrapped connection object for arbitrary queries to test
    PGconn *conn = library.GetConnection();

    // Check to see if tables exist
    PGresult* res_tables = PQexec(conn, "SELECT table_name FROM information_schema.tables WHERE table_name IN ('tracks', 'playlists', 'tracks_playlists');");
    assert(PQntuples(res_tables) == 0);
    PQclear(res_tables);

    // Check to see if the tracks_playlists_insert_func procedure exists
    PGresult* res_func = PQexec(conn, "SELECT routine_name FROM information_schema.routines WHERE routine_name = 'tracks_playlists_insert_func';");
    assert(PQntuples(res_func) == 0);
    PQclear(res_func);

    // Check to see if the tracks_playlists_insert_trg trigger exists
    PGresult* res_trg = PQexec(conn, "SELECT trigger_name FROM information_schema.triggers WHERE trigger_name = 'tracks_playlists_insert_trg';");
    assert(PQntuples(res_trg) == 0);
    PQclear(res_trg);

    cout << "OK" << endl;

}

/**
 * \brief Ensure tracks can be added properly
 *
 * Maybe later I'll figure out how to verify that the date is correct
 */
void Test_Library_AddTrack()
{
    cout << "Test_Library_AddTrack... ";
    CLibrary library;

    // Make sure all tables and such exist
    library.PrepareDatabase();

    std::string id = library.AddTrack(track1);

    // We need the unwrapped connection object for arbitrary queries to test
    PGconn* conn = library.GetConnection();

    std::string query = "SELECT * FROM tracks WHERE id=";

    // id should never not be a numeric string but safety first
    char escaped_id[30];
    PQescapeStringConn(conn, escaped_id, id.c_str(), 30, 0);
    query.append(escaped_id);

    PGresult* res = PQexec(conn, query.c_str());

    // Is there actually a row inserted with our id?
    std::string new_id(PQgetvalue(res, 0, 0));
    assert(new_id == id);

    // Is it the same one (same filepath) that we entered?
    std::string filepath(PQgetvalue(res, 0, 1));
    assert(filepath == track1);

    PQclear(res);

    std::string query2 = "SELECT * FROM tracks_playlists WHERE track_id = " + id;
    res = PQexec(conn, query2.c_str());
    assert(PQntuples(res) == 1);

    PQclear(res);

    library.DestroyDatabase();

    cout << "OK" << endl;
}

/**
 * \brief Ensure playlists can be added properly
 */
void Test_Library_AddPlaylist()
{
    cout << "Test_Library_AddPlaylist... ";
    CLibrary library;

    // Make sure all tables and such exist
    library.PrepareDatabase();

    std::string id = library.AddPlaylist(playlist1);

    // We need the unwrapped connection object for arbitrary queries to test
    PGconn *conn = library.GetConnection();

    std::string query = "SELECT * FROM playlists WHERE id=";

    // id should never not be a numeric string but safety first
    char escaped_id[30];
    PQescapeStringConn(conn, escaped_id, id.c_str(), 30, 0);
    query.append(escaped_id);

    PGresult *res = PQexec(conn, query.c_str());

    // Is there actually a row inserted with our id?
    std::string new_id(PQgetvalue(res, 0, 0));
    assert(new_id == id);

    // Is it the same one (same title) that we entered?
    std::string title(PQgetvalue(res, 0, 1));
    assert(title == playlist1);

    PQclear(res);

    library.DestroyDatabase();

    cout << "OK" << endl;
}

/**
 * \brief Ensure tracks are removed from the database properly
 * This includes removal from all playlists they have been added to
 */
void Test_Library_RemoveTrack()
{
    cout << "Test_Library_RemoveTrack... ";
    CLibrary library;

    // Make sure all tables and such exist
    library.PrepareDatabase();

    std::string track1_id = library.AddTrack(track1);
    std::string track2_id = library.AddTrack(track2);

    std::string playlist_id = library.AddPlaylist("test");
    CPlaylist playlist(&library, playlist_id);
    playlist.AppendTrack(track1_id);
    playlist.AppendTrack(track2_id);

    PGconn *conn = library.GetConnection();

    std::string query = "SELECT * FROM tracks_playlists WHERE track_id = ";
    char escaped_track1_id[30];
    char escaped_track2_id[30];
    PQescapeStringConn(conn, escaped_track1_id, track1_id.c_str(), 30, 0);
    PQescapeStringConn(conn, escaped_track2_id, track2_id.c_str(), 30, 0);

    std::string query1 = query + escaped_track1_id;
    std::string query2 = query + escaped_track2_id;
    PGresult *res1 = PQexec(conn, query1.c_str());
    PGresult *res2 = PQexec(conn, query2.c_str());

    assert(PQntuples(res1) == 2);
    assert(PQntuples(res2) == 2);

    PQclear(res1);
    PQclear(res2);

    library.RemoveTrack(track1_id);
    library.RemoveTrack(track2_id);

    std::string query3 = "SELECT * FROM tracks WHERE id = ";
    query3.append(escaped_track1_id);
    std::string query4 = "SELECT * FROM tracks WHERE id = ";
    query3.append(escaped_track2_id);
    PGresult *res3 = PQexec(conn, query3.c_str());
    PGresult *res4 = PQexec(conn, query4.c_str());

    res1 = PQexec(conn, query1.c_str());
    res2 = PQexec(conn, query2.c_str());

    assert(PQntuples(res1) == 0);
    assert(PQntuples(res2) == 0);
    assert(PQntuples(res3) == 0);
    assert(PQntuples(res4) == 0);

    PQclear(res1);
    PQclear(res2);
    PQclear(res3);
    PQclear(res4);

    library.DestroyDatabase();

    cout << "OK" << endl;
}

/**
 * \brief Ensure playlists are properly removed as well as any records of track membership
 */
void Test_Library_RemovePlaylist()
{
    cout << "Test_Library_RemovePlaylist... ";
    CLibrary library;

    // Make sure all tables and such exist
    library.PrepareDatabase();

    std::string track1_id = library.AddTrack(track1);
    std::string track2_id = library.AddTrack(track2);

    std::string playlist_id = library.AddPlaylist("test");
    CPlaylist playlist(&library, playlist_id);
    playlist.AppendTrack(track1_id);
    playlist.AppendTrack(track2_id);

    PGconn *conn = library.GetConnection();

    std::string query = "SELECT * FROM tracks_playlists WHERE playlist_id = ";
    char escaped_playlist_id[30];
    PQescapeStringConn(conn, escaped_playlist_id, playlist_id.c_str(), 30, 0);

    query.append(escaped_playlist_id);
    PGresult *res = PQexec(conn, query.c_str());

    assert(PQntuples(res) == 2);

    PQclear(res);

    library.RemovePlaylist(playlist_id);

    res = PQexec(conn, query.c_str());

    assert(PQntuples(res) == 0);

    PQclear(res);

    library.DestroyDatabase();

    cout << "OK" << endl;
}

/**
 * \brief Ensure playlist objects are constructed properly
 *
 * Note: This isn't done. Verify that non-empty playlists are properly
 * populated with Track objects when the Track class is implemented
 */
void Test_Playlist_Constructors()
{
    cout << "Test_Playlist_Constructors... ";
    CLibrary library;

    // Make sure all tables and such exist
    library.PrepareDatabase();

    // Should create an empty playlist
    CPlaylist playlist(&library);
    assert(playlist.GetId() == "temp");
    assert(playlist.GetTitle() == "working playlist");
    assert(playlist.GetLength() == "0");
    assert(playlist.GetLibrary() == &library);

    // Should create an object for the default playlist in db
    CPlaylist playlist2(&library, "1");
    assert(playlist2.GetId() == "1");
    assert(playlist2.GetTitle() == "library");
    assert(playlist2.GetLength() == "0");
    assert(playlist2.GetLibrary() == &library);

    library.DestroyDatabase();

    cout << "OK" << endl;
}

/**
 * \brief Ensure tracks can be properly appended to playlists
 *
 * Note: This isn't done. Verify that Track objects are properly appended
 * to their containers when the Track Class is implemented
 */
void Test_Playlist_AppendTrack()
{
    cout << "Test_Playlist_AppendTrack... ";
    CLibrary library;

    // Make sure all tables and such exist
    library.PrepareDatabase();

    // Adding tracks that can be added to playlists
    std::string track1_id = library.AddTrack(track1);
    std::string track2_id = library.AddTrack(track2);

    CPlaylist db_playlist(&library, "1"); // a playlist from the database
    CPlaylist temp_playlist(&library);    // a temp playlist not in the database

    std::string association1 = db_playlist.AppendTrack(track1_id);
    std::string association2 = db_playlist.AppendTrack(track2_id);

    std::string association3 = temp_playlist.AppendTrack(track1_id);
    std::string association4 = temp_playlist.AppendTrack(track2_id);

    assert(association3 == "temp");
    assert(association4 == "temp");

    // Insert logic to make sure Track objects were appropriately added to the container
    // for both playlists

    // Verify that the proper database records were created
    if (db_playlist.GetId() != "temp")
    {
        PGconn *conn = library.GetConnection();

        std::string query = "SELECT id, playlist_id, track_id, position FROM tracks_playlists WHERE playlist_id = ";

        char escaped_playlist_id[30];
        PQescapeStringConn(conn, escaped_playlist_id, db_playlist.GetId().c_str(), 30, 0);

        query.append(escaped_playlist_id);

        PGresult *res = PQexec(conn, query.c_str());

        // 0 and 1 should be auto-created when tracks are added
        // 2 and 3 are the new records we just added
        std::string association1_id(PQgetvalue(res, 2, 0));
        std::string association1_playlist_id(PQgetvalue(res, 2, 1));
        std::string association1_track_id(PQgetvalue(res, 2, 2));
        std::string association1_position(PQgetvalue(res, 2, 3));
        std::string association2_id(PQgetvalue(res, 3, 0));
        std::string association2_playlist_id(PQgetvalue(res, 3, 1));
        std::string association2_track_id(PQgetvalue(res, 3, 2));
        std::string association2_position(PQgetvalue(res, 3, 3));

        assert(association1 == association1_id);
        assert(db_playlist.GetId() == association1_playlist_id);
        assert(track1_id == association1_track_id);
        assert("3" == association1_position);
        assert(association2 == association2_id);
        assert(db_playlist.GetId() == association2_playlist_id);
        assert(track2_id == association2_track_id);
        assert("4" == association2_position);

        PQclear(res);
    }
    else
    {
        assert(association1 == "temp");
        assert(association2 == "temp");
    }

    library.DestroyDatabase();

    cout << "OK" << endl;
}
<commit_msg>lol so this test was not actually testing anything<commit_after>/**
 * \file tests.cpp
 * \author Matt Hammerly
 * \brief This file contains int main() which will run tests as they're written
 */
#include <iostream>
#include <cassert>
#include "Library.h"
#include "Track.h"
#include "Playlist.h"
#include "tests.h"

using std::cout; using std::endl;

/// File location of one track
const std::string track1 = "/home/matt/hdd/dev/cpp/musicmanager/test_data/Gregory And The Hawk/The Boats & Birds EP/01 Boats & Birds.mp3";

/// File location of another track
const std::string track2 = "/home/matt/hdd/dev/cpp/musicmanager/test_data/Beirut/The Flying Club Cup/05 la banlieue.m4a";

/// Title of one playlist
const std::string playlist1 = "test1";

/**
 * \brief Main entry point of program, where tests will be run
 */
int main()
{

    Test_Library_Constructor();

    Test_Library_PrepareDatabase();

    Test_Library_DestroyDatabase();

    Test_Library_AddTrack();

    Test_Library_AddPlaylist();

    Test_Library_RemoveTrack();

    Test_Library_RemovePlaylist();

    Test_Playlist_Constructors();

    Test_Playlist_AppendTrack();

    // So I can poke around manually after running tests
    //CLibrary library;
    //library.PrepareDatabase();

    return 0;
}

/**
 * \brief I mean, there's constructor logic, let's test it.
 */
void Test_Library_Constructor()
{
    cout << "Test_Library_Constructor... ";
    CLibrary library;
    assert(library.GetStatus() == CONNECTION_OK);
    cout << "OK" << endl;
}

/**
 * \brief Ensure all expected database objects are created
 */
void Test_Library_PrepareDatabase()
{
    cout << "Test_Library_PrepareDatabase... ";
    CLibrary library;

    library.PrepareDatabase();

    // We need the unwrapped connection object for arbitrary queries to test
    PGconn *conn = library.GetConnection();

    // Check to see if tables exist
    // Three rows should be returned; one for tracks, one for playlists, one for tracks_playlists
    PGresult* res_tables = PQexec(conn, "SELECT table_name FROM information_schema.tables WHERE table_name IN ('tracks', 'playlists', 'tracks_playlists');");
    assert(PQntuples(res_tables) == 3);
    PQclear(res_tables);

    // Check to see if the tracks_playlists_insert_func procedure exists
    PGresult* res_func = PQexec(conn, "SELECT routine_name FROM information_schema.routines WHERE routine_name = 'tracks_playlists_insert_func';");
    assert(PQntuples(res_func) == 1);
    PQclear(res_func);

    // Check to see if the tracks_playlists_insert_trg trigger exists
    // Two records should be returned; one for ON INSERT, one for ON DELETE
    PGresult* res_trg = PQexec(conn, "SELECT trigger_name FROM information_schema.triggers WHERE trigger_name = 'tracks_playlists_insert_trg';");
    assert(PQntuples(res_trg) == 2);
    PQclear(res_trg);

    // Check to see if the default library playlist was properly created
    PGresult *res_playlist = PQexec(conn, "SELECT id, title, length FROM playlists WHERE id=1");
    assert(PQntuples(res_playlist) == 1);
    PQclear(res_playlist);

    // clean up, I guess
    library.DestroyDatabase();

    cout << "OK" << endl;
}

/**
 * \brief Ensure database objects are deleted when asked
 */
void Test_Library_DestroyDatabase()
{
    cout << "Test_Library_DestroyDatabase... ";
    CLibrary library;

    // Make sure all tables and such exist
    library.PrepareDatabase();

    library.DestroyDatabase();

    // We need the unwrapped connection object for arbitrary queries to test
    PGconn *conn = library.GetConnection();

    // Check to see if tables exist
    PGresult* res_tables = PQexec(conn, "SELECT table_name FROM information_schema.tables WHERE table_name IN ('tracks', 'playlists', 'tracks_playlists');");
    assert(PQntuples(res_tables) == 0);
    PQclear(res_tables);

    // Check to see if the tracks_playlists_insert_func procedure exists
    PGresult* res_func = PQexec(conn, "SELECT routine_name FROM information_schema.routines WHERE routine_name = 'tracks_playlists_insert_func';");
    assert(PQntuples(res_func) == 0);
    PQclear(res_func);

    // Check to see if the tracks_playlists_insert_trg trigger exists
    PGresult* res_trg = PQexec(conn, "SELECT trigger_name FROM information_schema.triggers WHERE trigger_name = 'tracks_playlists_insert_trg';");
    assert(PQntuples(res_trg) == 0);
    PQclear(res_trg);

    cout << "OK" << endl;

}

/**
 * \brief Ensure tracks can be added properly
 *
 * Maybe later I'll figure out how to verify that the date is correct
 */
void Test_Library_AddTrack()
{
    cout << "Test_Library_AddTrack... ";
    CLibrary library;

    // Make sure all tables and such exist
    library.PrepareDatabase();

    std::string id = library.AddTrack(track1);

    // We need the unwrapped connection object for arbitrary queries to test
    PGconn* conn = library.GetConnection();

    std::string query = "SELECT * FROM tracks WHERE id=";

    // id should never not be a numeric string but safety first
    char escaped_id[30];
    PQescapeStringConn(conn, escaped_id, id.c_str(), 30, 0);
    query.append(escaped_id);

    PGresult* res = PQexec(conn, query.c_str());

    // Is there actually a row inserted with our id?
    std::string new_id(PQgetvalue(res, 0, 0));
    assert(new_id == id);

    // Is it the same one (same filepath) that we entered?
    std::string filepath(PQgetvalue(res, 0, 1));
    assert(filepath == track1);

    PQclear(res);

    std::string query2 = "SELECT * FROM tracks_playlists WHERE track_id = " + id;
    res = PQexec(conn, query2.c_str());
    assert(PQntuples(res) == 1);

    PQclear(res);

    library.DestroyDatabase();

    cout << "OK" << endl;
}

/**
 * \brief Ensure playlists can be added properly
 */
void Test_Library_AddPlaylist()
{
    cout << "Test_Library_AddPlaylist... ";
    CLibrary library;

    // Make sure all tables and such exist
    library.PrepareDatabase();

    std::string id = library.AddPlaylist(playlist1);

    // We need the unwrapped connection object for arbitrary queries to test
    PGconn *conn = library.GetConnection();

    std::string query = "SELECT * FROM playlists WHERE id=";

    // id should never not be a numeric string but safety first
    char escaped_id[30];
    PQescapeStringConn(conn, escaped_id, id.c_str(), 30, 0);
    query.append(escaped_id);

    PGresult *res = PQexec(conn, query.c_str());

    // Is there actually a row inserted with our id?
    std::string new_id(PQgetvalue(res, 0, 0));
    assert(new_id == id);

    // Is it the same one (same title) that we entered?
    std::string title(PQgetvalue(res, 0, 1));
    assert(title == playlist1);

    PQclear(res);

    library.DestroyDatabase();

    cout << "OK" << endl;
}

/**
 * \brief Ensure tracks are removed from the database properly
 * This includes removal from all playlists they have been added to
 */
void Test_Library_RemoveTrack()
{
    cout << "Test_Library_RemoveTrack... ";
    CLibrary library;

    // Make sure all tables and such exist
    library.PrepareDatabase();

    std::string track1_id = library.AddTrack(track1);
    std::string track2_id = library.AddTrack(track2);

    std::string playlist_id = library.AddPlaylist("test");
    CPlaylist playlist(&library, playlist_id);
    playlist.AppendTrack(track1_id);
    playlist.AppendTrack(track2_id);

    PGconn *conn = library.GetConnection();

    std::string query = "SELECT * FROM tracks_playlists WHERE track_id = ";
    char escaped_track1_id[30];
    char escaped_track2_id[30];
    PQescapeStringConn(conn, escaped_track1_id, track1_id.c_str(), 30, 0);
    PQescapeStringConn(conn, escaped_track2_id, track2_id.c_str(), 30, 0);

    std::string query1 = query + escaped_track1_id;
    std::string query2 = query + escaped_track2_id;
    PGresult *res1 = PQexec(conn, query1.c_str());
    PGresult *res2 = PQexec(conn, query2.c_str());

    assert(PQntuples(res1) == 2);
    assert(PQntuples(res2) == 2);

    PQclear(res1);
    PQclear(res2);

    library.RemoveTrack(track1_id);
    library.RemoveTrack(track2_id);

    std::string query3 = "SELECT * FROM tracks WHERE id = ";
    query3.append(escaped_track1_id);
    std::string query4 = "SELECT * FROM tracks WHERE id = ";
    query3.append(escaped_track2_id);
    PGresult *res3 = PQexec(conn, query3.c_str());
    PGresult *res4 = PQexec(conn, query4.c_str());

    res1 = PQexec(conn, query1.c_str());
    res2 = PQexec(conn, query2.c_str());

    assert(PQntuples(res1) == 0);
    assert(PQntuples(res2) == 0);
    assert(PQntuples(res3) == 0);
    assert(PQntuples(res4) == 0);

    PQclear(res1);
    PQclear(res2);
    PQclear(res3);
    PQclear(res4);

    library.DestroyDatabase();

    cout << "OK" << endl;
}

/**
 * \brief Ensure playlists are properly removed as well as any records of track membership
 */
void Test_Library_RemovePlaylist()
{
    cout << "Test_Library_RemovePlaylist... ";
    CLibrary library;

    // Make sure all tables and such exist
    library.PrepareDatabase();

    std::string track1_id = library.AddTrack(track1);
    std::string track2_id = library.AddTrack(track2);

    std::string playlist_id = library.AddPlaylist("test");
    CPlaylist playlist(&library, playlist_id);
    playlist.AppendTrack(track1_id);
    playlist.AppendTrack(track2_id);

    PGconn *conn = library.GetConnection();

    std::string playlist_query = "SELECT * FROM playlists WHERE id = " + playlist_id;
    PGresult *res = PQexec(conn, playlist_query.c_str());
    assert(PQntuples(res) == 1);
    PQclear(res);

    std::string query = "SELECT * FROM tracks_playlists WHERE playlist_id = ";
    char escaped_playlist_id[30];
    PQescapeStringConn(conn, escaped_playlist_id, playlist_id.c_str(), 30, 0);

    query.append(escaped_playlist_id);
    res = PQexec(conn, query.c_str());

    assert(PQntuples(res) == 2);

    PQclear(res);

    library.RemovePlaylist(playlist_id);

    res = PQexec(conn, playlist_query.c_str());
    assert(PQntuples(res) == 0);
    PQclear(res);

    res = PQexec(conn, query.c_str());

    assert(PQntuples(res) == 0);

    PQclear(res);

    library.DestroyDatabase();

    cout << "OK" << endl;
}

/**
 * \brief Ensure playlist objects are constructed properly
 *
 * Note: This isn't done. Verify that non-empty playlists are properly
 * populated with Track objects when the Track class is implemented
 */
void Test_Playlist_Constructors()
{
    cout << "Test_Playlist_Constructors... ";
    CLibrary library;

    // Make sure all tables and such exist
    library.PrepareDatabase();

    // Should create an empty playlist
    CPlaylist playlist(&library);
    assert(playlist.GetId() == "temp");
    assert(playlist.GetTitle() == "working playlist");
    assert(playlist.GetLength() == "0");
    assert(playlist.GetLibrary() == &library);

    // Should create an object for the default playlist in db
    CPlaylist playlist2(&library, "1");
    assert(playlist2.GetId() == "1");
    assert(playlist2.GetTitle() == "library");
    assert(playlist2.GetLength() == "0");
    assert(playlist2.GetLibrary() == &library);

    library.DestroyDatabase();

    cout << "OK" << endl;
}

/**
 * \brief Ensure tracks can be properly appended to playlists
 *
 * Note: This isn't done. Verify that Track objects are properly appended
 * to their containers when the Track Class is implemented
 */
void Test_Playlist_AppendTrack()
{
    cout << "Test_Playlist_AppendTrack... ";
    CLibrary library;

    // Make sure all tables and such exist
    library.PrepareDatabase();

    // Adding tracks that can be added to playlists
    std::string track1_id = library.AddTrack(track1);
    std::string track2_id = library.AddTrack(track2);

    CPlaylist db_playlist(&library, "1"); // a playlist from the database
    CPlaylist temp_playlist(&library);    // a temp playlist not in the database

    std::string association1 = db_playlist.AppendTrack(track1_id);
    std::string association2 = db_playlist.AppendTrack(track2_id);

    std::string association3 = temp_playlist.AppendTrack(track1_id);
    std::string association4 = temp_playlist.AppendTrack(track2_id);

    assert(association3 == "temp");
    assert(association4 == "temp");

    // Insert logic to make sure Track objects were appropriately added to the container
    // for both playlists

    // Verify that the proper database records were created
    if (db_playlist.GetId() != "temp")
    {
        PGconn *conn = library.GetConnection();

        std::string query = "SELECT id, playlist_id, track_id, position FROM tracks_playlists WHERE playlist_id = ";

        char escaped_playlist_id[30];
        PQescapeStringConn(conn, escaped_playlist_id, db_playlist.GetId().c_str(), 30, 0);

        query.append(escaped_playlist_id);

        PGresult *res = PQexec(conn, query.c_str());

        // 0 and 1 should be auto-created when tracks are added
        // 2 and 3 are the new records we just added
        std::string association1_id(PQgetvalue(res, 2, 0));
        std::string association1_playlist_id(PQgetvalue(res, 2, 1));
        std::string association1_track_id(PQgetvalue(res, 2, 2));
        std::string association1_position(PQgetvalue(res, 2, 3));
        std::string association2_id(PQgetvalue(res, 3, 0));
        std::string association2_playlist_id(PQgetvalue(res, 3, 1));
        std::string association2_track_id(PQgetvalue(res, 3, 2));
        std::string association2_position(PQgetvalue(res, 3, 3));

        assert(association1 == association1_id);
        assert(db_playlist.GetId() == association1_playlist_id);
        assert(track1_id == association1_track_id);
        assert("3" == association1_position);
        assert(association2 == association2_id);
        assert(db_playlist.GetId() == association2_playlist_id);
        assert(track2_id == association2_track_id);
        assert("4" == association2_position);

        PQclear(res);
    }
    else
    {
        assert(association1 == "temp");
        assert(association2 == "temp");
    }

    library.DestroyDatabase();

    cout << "OK" << endl;
}
<|endoftext|>