<commit_before>//
//  banded_global_aligner.hpp
//
//  Contains objects to support Aligner in performing banded global alignment against
//  a graph.
//

#ifndef banded_global_aligner_hpp
#define banded_global_aligner_hpp

#include <stdio.h>
#include <ctype.h>
#include <iostream>
#include <vector>
#include <unordered_set>
#include <unordered_map>
#include <list>
#include "vg.pb.h"

using namespace std;

namespace vg {

    // This class is the outward-facing interface for banded global graph alignment. It computes
    // optimal alignment of a DNA sequence to a DAG with POA. The alignment will start at any source
    // node in the graph and end at any sink node. It is also restricted to falling within a certain
    // diagonal band from the start node. Any signed integer type can be used for the dynamic programming
    // matrices, but there are no checks for overflow.
    template <class IntType>
    class BandedGlobalAligner {
    public:

        /*
         * Initializes banded alignment
         *
         * Args:
         *  alignment                   empty alignment with a sequence (and possibly base qualities)
         *  g                           graph to align to
         *  band_padding                width to expand band by
         *  permissive_banding          expand band, not necessarily symmetrically, to allow all node paths
         *  adjust_for_base_quality     perform base quality adjusted alignment (see QualAdjAligner)
         */
        BandedGlobalAligner(Alignment& alignment, Graph& g,
                            int64_t band_padding, bool permissive_banding = false,
                            bool adjust_for_base_quality = false);

        /*
         * Initializes banded multi-alignment, which performs the top scoring alternate alignments in addition
         * to the primary
         *
         * Args:
         *  alignment                   empty alignment with a sequence (and possibly base qualities)
         *  g                           graph to align to
         *  alt_alignments              an empty vector to store alternate alignments in, the first element
         *                              will be a copy of the primary alignment
         *  max_multi_alns              the maximum number of alternate alignments (including the primary)
         *  band_padding                width to expand band by
         *  permissive_banding          expand band, not necessarily symmetrically, to allow all node paths
         *  adjust_for_base_quality     perform base quality adjusted alignment (see QualAdjAligner)
         */
        BandedGlobalAligner(Alignment& alignment, Graph& g,
                            vector<Alignment>& alt_alignments, int64_t max_multi_alns,
                            int64_t band_padding, bool permissive_banding = false,
                            bool adjust_for_base_quality = false);

        ~BandedGlobalAligner();

        /*
         * Adds path and score to the alignment object given in the constructor. If a multi-alignment
         * vector was also supplied, fills the
         *
         * Args:
         *  score_mat   matrix of match/mismatch scores from Aligner (if performing base quality
         *              adjusted alignment, use QualAdjAligner's adjusted score matrix)
         *  nt_table    table of indices by DNA char from Aligner
         *  gap_open    gap open penalty from Algner (if performing base quality adjusted alignment,
         *              use QualAdjAligner's scaled penalty)
         *  gap_extend  gap extension penalty from Algner (if performing base quality adjusted alignment,
         *              use QualAdjAligner's scaled penalty)
         *
         */
        void align(int8_t* score_mat, int8_t* nt_table, int8_t gap_open, int8_t gap_extend);
        // these are not IntType so that they can interact well with Aligner

    private:

        class BAMatrix;
        class BABuilder;
        class AltTracebackStack;

        // matrices used in Smith-Waterman-Gotoh alignment algorithm
        enum matrix_t {Match, InsertCol, InsertRow};

        // the primary alignment
        Alignment& alignment;
        // vector for alternate alignments, or null if not making any
        vector<Alignment>* alt_alignments;
        int64_t max_multi_alns;
        // perform quality adjusted alignments
        bool adjust_for_base_quality;

        vector<BAMatrix*> banded_matrices;

        unordered_map<int64_t, int64_t> node_id_to_idx;
        vector<Node*> topological_order;
        unordered_set<Node*> source_nodes;
        unordered_set<Node*> sink_nodes;

        // internal constructor that the others funnel into
        BandedGlobalAligner(Alignment& alignment, Graph& g,
                            vector<Alignment>* alt_alignments, int64_t max_multi_alns,
                            int64_t band_padding, bool permissive_banding = false,
                            bool adjust_for_base_quality = false);

        // traceback an alignment
        void traceback(int8_t* score_mat, int8_t* nt_table, int8_t gap_open, int8_t gap_extend, IntType min_inf);

        // construction functions
        void graph_edge_lists(Graph& g, bool outgoing_edges, vector<vector<int64_t>>& out_edge_list);
        void topological_sort(Graph& g, vector<vector<int64_t>>& node_edges_out, vector<Node*>& out_topological_order);
        void path_lengths_to_sinks(const string& read, vector<vector<int64_t>>& node_edges_in,
                                   vector<int64_t>& shortest_path_to_sink, vector<int64_t>& longest_path_to_sink);
        void find_banded_paths(const string& read, bool permissive_banding, vector<vector<int64_t>>& node_edges_in,
                               vector<vector<int64_t>>& node_edges_out, int64_t band_padding,
                               vector<bool>& node_masked, vector<pair<int64_t, int64_t>>& band_ends);
        void shortest_seq_paths(vector<vector<int64_t>>& node_edges_out, vector<int64_t>& seq_lens_out,
                                unordered_set<Node*> source_nodes);
    };

    // the band from the DP matrix for one node in the graph
    template <class IntType>
    class BandedGlobalAligner<IntType>::BAMatrix {
    private:

        // these indicate the diagonals in this matrix that the band passes through
        // the bottom index is inclusive
        int64_t top_diag;
        int64_t bottom_diag;

        Node* node;

        Alignment& alignment;

        // length of shortest sequence leading to matrix from a source node
        int64_t cumulative_seq_len;

        BAMatrix** seeds;
        int64_t num_seeds;

        IntType* match;
        IntType* insert_col;
        IntType* insert_row;

        void traceback_internal(BABuilder& builder, AltTracebackStack& traceback_stack, int64_t start_row,
                                int64_t start_col, matrix_t start_mat, bool in_lead_gap, int8_t* score_mat,
                                int8_t* nt_table, int8_t gap_open, int8_t gap_extend, bool qual_adjusted,
                                IntType min_inf);

        void print_matrix(matrix_t which_mat);
        void print_band(matrix_t which_mat);

    public:
        BAMatrix(Alignment& alignment, Node* node, int64_t top_diag, int64_t bottom_diag,
                 BAMatrix** seeds, int64_t num_seeds, int64_t cumulative_seq_len);
        ~BAMatrix();

        // use DP to fill the band with alignment scores
        void fill_matrix(int8_t* score_mat, int8_t* nt_table, int8_t gap_open, int8_t gap_extend, bool qual_adjusted,
                         IntType min_inf);

        void traceback(BABuilder& builder, AltTracebackStack& traceback_stack, matrix_t start_mat, int8_t* score_mat,
                       int8_t* nt_table, int8_t gap_open, int8_t gap_extend, bool qual_adjusted, IntType min_inf);

        // debugging functions
        void print_full_matrices();
        void print_rectangularized_bands();

        friend class BABuilder;
        friend class AltTracebackStack; // not a fan of this one, but constructor ugly without it
    };

    // maintains a stack of directions to find the top scoring tracebacks
    template <class IntType>
    class BandedGlobalAligner<IntType>::AltTracebackStack {
    public:
        AltTracebackStack(int64_t max_multi_alns, vector<BAMatrix*> sink_node_matrices);
        ~AltTracebackStack();

        // get the start position of the current alignment and advance to the first deflection
        inline void get_alignment_start(int64_t& node_id, matrix_t& matrix);

        // advance to the next alternate alignment
        inline void next();
        inline bool has_next();

        // check if a deflection from the current traceback
        inline void propose_deflection(const IntType score, const int64_t from_node_id, const int64_t row_idx,
                                       const int64_t col_idx, const int64_t to_node_id, const matrix_t to_matrix);

        // score of the current traceback
        inline IntType current_traceback_score();

        // are these the coordinates of the next deflection?
        inline bool at_next_deflection(int64_t node_id, int64_t row_idx, int64_t col_idx);

        // get the matrix to deflect to and advance to the next deflection
        inline BandedGlobalAligner::matrix_t deflect_to_matrix();
        // additionally get the node id if at a node boundary
        inline BandedGlobalAligner::matrix_t deflect_to_matrix(int64_t& to_node_id);

    private:
        class Deflection;

        int64_t max_multi_alns;
        // pairs contain scores of alignments and the places where their traceback differs from the optimum
        list<pair<vector<Deflection>, IntType>> alt_tracebacks;

        typename list<pair<vector<Deflection>, IntType>>::iterator curr_traceback;
        typename vector<Deflection>::iterator curr_deflxn;

        inline void insert_traceback(const vector<Deflection>& traceback_prefix, const IntType score,
                                     const int64_t from_node_id, const int64_t row_idx,
                                     const int64_t col_idx, const int64_t to_node_id, const matrix_t to_matrix);
    };

    template <class IntType>
    class BandedGlobalAligner<IntType>::AltTracebackStack::Deflection {
    public:
        Deflection(const int64_t from_node_id, const int64_t row_idx, const int64_t col_idx,
                   const int64_t to_node_id, const matrix_t to_matrix);
        ~Deflection();

        const int64_t from_node_id;
        // coordinates from rectangularized band (not original matrix)
        const int64_t row_idx;
        const int64_t col_idx;
        // matrix and node to deflect to
        const int64_t to_node_id;
        const matrix_t to_matrix;
    };

    // translates a traceback path into a Path object and stores it in an Alignment object
    template <class IntType>
    class BandedGlobalAligner<IntType>::BABuilder {
    private:
        Alignment& alignment;

        list<Mapping> node_mappings;
        list<Edit> mapping_edits;

        matrix_t matrix_state;
        bool matching;
        Node* current_node;
        int64_t edit_length;
        int64_t edit_read_end_idx;

        void finish_current_edit();
        void finish_current_node();

    public:
        BABuilder(Alignment& alignment);
        ~BABuilder();

        // add next step in traceback
        void update_state(matrix_t matrix, Node* node, int64_t read_idx, int64_t node_idx);
        // call after concluding traceback to finish adding edits to alignment
        void finalize_alignment();
    };

    // define aligners for allowed integer types
    template class BandedGlobalAligner<int8_t>;
    template class BandedGlobalAligner<int16_t>;
    template class BandedGlobalAligner<int32_t>;
    template class BandedGlobalAligner<int64_t>;
}

#endif /* banded_global_aligner_hpp */

<commit_msg>ReadTheDocs documentation for banded aligner<commit_after>//
//  banded_global_aligner.hpp
//
//  Contains objects to support Aligner in performing banded global alignment against
//  a graph.
//

#ifndef banded_global_aligner_hpp
#define banded_global_aligner_hpp

#include <stdio.h>
#include <ctype.h>
#include <iostream>
#include <vector>
#include <unordered_set>
#include <unordered_map>
#include <list>
#include "vg.pb.h"

using namespace std;

namespace vg {

    /* The outward-facing interface for banded global graph alignment. It computes optimal alignment
     * of a DNA sequence to a DAG with POA. The alignment will start at any source node in the graph and
     * end at any sink node. It is also restricted to falling within a certain diagonal band from the
     * start node. Any signed integer type can be used for the dynamic programming matrices, but there
     * are no checks for overflow.
     *
     */
    template <class IntType>
    class BandedGlobalAligner {
    public:

        /// Initializes banded alignment
        ///
        /// Args:
        /// alignment                   empty alignment with a sequence (and possibly base qualities)
        ///  g                           graph to align to
        ///  band_padding                width to expand band by
        ///  permissive_banding          expand band, not necessarily symmetrically, to allow all node paths
        ///  adjust_for_base_quality     perform base quality adjusted alignment (see QualAdjAligner)
        ///
        BandedGlobalAligner(Alignment& alignment, Graph& g,
                            int64_t band_padding, bool permissive_banding = false,
                            bool adjust_for_base_quality = false);

        /// Initializes banded multi-alignment, which computes the top scoring alternate alignments in addition
        /// to the optimal alignment
        ///
        /// Args:
        ///  alignment                   empty alignment with a sequence (and possibly base qualities)
        ///  g                           graph to align to
        ///  alt_alignments              an empty vector to store alternate alignments in, the first element
        ///                              will be a copy of the primary alignment
        ///  max_multi_alns              the maximum number of alternate alignments (including the primary)
        ///  band_padding                width to expand band by
        ///  permissive_banding          expand band, not necessarily symmetrically, to allow all node paths
        ///  adjust_for_base_quality     perform base quality adjusted alignment (see QualAdjAligner)
        BandedGlobalAligner(Alignment& alignment, Graph& g,
                            vector<Alignment>& alt_alignments, int64_t max_multi_alns,
                            int64_t band_padding, bool permissive_banding = false,
                            bool adjust_for_base_quality = false);

        ~BandedGlobalAligner();

        /// Adds path and score to the alignment object given in the constructor. If a multi-alignment
        /// vector was also supplied, fills the vector with the top scoring alignments as well.
        ///
        /// Note: the score arguments are not <IntType> so that they can interact easily with the Aligner
        ///
        /// Args:
        ///  score_mat   matrix of match/mismatch scores from Aligner (if performing base quality
        ///              adjusted alignment, use QualAdjAligner's adjusted score matrix)
        ///  nt_table    table of indices by DNA char from Aligner
        ///  gap_open    gap open penalty from Algner (if performing base quality adjusted alignment,
        ///              use QualAdjAligner's scaled penalty)
        ///  gap_extend  gap extension penalty from Algner (if performing base quality adjusted alignment,
        ///              use QualAdjAligner's scaled penalty)
        void align(int8_t* score_mat, int8_t* nt_table, int8_t gap_open, int8_t gap_extend);

    private:

        class BAMatrix;
        class BABuilder;
        class AltTracebackStack;

        /// Matrices used in Smith-Waterman-Gotoh alignment algorithm
        enum matrix_t {Match, InsertCol, InsertRow};

        /// The primary alignment
        Alignment& alignment;
        /// Vector for alternate alignments, or null if not making any
        vector<Alignment>* alt_alignments;
        /// Number of alignments to compute, including the optimal alignment
        int64_t max_multi_alns;
        /// Use base quality adjusted scoring for alignments?
        bool adjust_for_base_quality;

        /// Dynamic programming matrices for each node
        vector<BAMatrix*> banded_matrices;

        /// Map from node IDs to the index used in internal vectors
        unordered_map<int64_t, int64_t> node_id_to_idx;
        /// A topological ordering of the nodes
        vector<Node*> topological_order;
        /// Source nodes in the graph
        unordered_set<Node*> source_nodes;
        /// Sink nodes in the graph
        unordered_set<Node*> sink_nodes;

        /// Internal constructor that the public constructors funnel into
        BandedGlobalAligner(Alignment& alignment, Graph& g,
                            vector<Alignment>* alt_alignments, int64_t max_multi_alns,
                            int64_t band_padding, bool permissive_banding = false,
                            bool adjust_for_base_quality = false);

        /// Traceback through dynamic programming matrices to compute alignment
        void traceback(int8_t* score_mat, int8_t* nt_table, int8_t gap_open, int8_t gap_extend, IntType min_inf);

        /// Constructor helper function: converts Graph object into adjacency list representation
        void graph_edge_lists(Graph& g, bool outgoing_edges, vector<vector<int64_t>>& out_edge_list);
        /// Constructor helper function: compute topoligical ordering
        void topological_sort(Graph& g, vector<vector<int64_t>>& node_edges_out, vector<Node*>& out_topological_order);
        /// Constructor helper function: compute the longest and shortest path to a sink for each node
        void path_lengths_to_sinks(const string& read, vector<vector<int64_t>>& node_edges_in,
                                   vector<int64_t>& shortest_path_to_sink, vector<int64_t>& longest_path_to_sink);
        /// Constructor helper function: compute which diagonals the bands cover on each node's matrix
        void find_banded_paths(const string& read, bool permissive_banding, vector<vector<int64_t>>& node_edges_in,
                               vector<vector<int64_t>>& node_edges_out, int64_t band_padding,
                               vector<bool>& node_masked, vector<pair<int64_t, int64_t>>& band_ends);
        /// Constructor helper function: compute the shortest path from a source to each node
        void shortest_seq_paths(vector<vector<int64_t>>& node_edges_out, vector<int64_t>& seq_lens_out,
                                unordered_set<Node*> source_nodes);
    };

    /*
     * Represents the band from the DP matrix for one node in the graph
     *
     */
    template <class IntType>
    class BandedGlobalAligner<IntType>::BAMatrix {
    private:

        // these indicate the diagonals in this matrix that the band passes through
        // the bottom index is inclusive
        int64_t top_diag;
        int64_t bottom_diag;

        Node* node;

        Alignment& alignment;

        // length of shortest sequence leading to matrix from a source node
        int64_t cumulative_seq_len;

        BAMatrix** seeds;
        int64_t num_seeds;

        IntType* match;
        IntType* insert_col;
        IntType* insert_row;

        void traceback_internal(BABuilder& builder, AltTracebackStack& traceback_stack, int64_t start_row,
                                int64_t start_col, matrix_t start_mat, bool in_lead_gap, int8_t* score_mat,
                                int8_t* nt_table, int8_t gap_open, int8_t gap_extend, bool qual_adjusted,
                                IntType min_inf);

        void print_matrix(matrix_t which_mat);
        void print_band(matrix_t which_mat);

    public:
        BAMatrix(Alignment& alignment, Node* node, int64_t top_diag, int64_t bottom_diag,
                 BAMatrix** seeds, int64_t num_seeds, int64_t cumulative_seq_len);
        ~BAMatrix();

        /// Use DP to fill the band with alignment scores
        void fill_matrix(int8_t* score_mat, int8_t* nt_table, int8_t gap_open, int8_t gap_extend, bool qual_adjusted,
                         IntType min_inf);

        /// Traceback through the band after using DP to fill it
        void traceback(BABuilder& builder, AltTracebackStack& traceback_stack, matrix_t start_mat, int8_t* score_mat,
                       int8_t* nt_table, int8_t gap_open, int8_t gap_extend, bool qual_adjusted, IntType min_inf);

        /// Debugging function
        void print_full_matrices();
        /// Debugging function
        void print_rectangularized_bands();

        friend class BABuilder;
        friend class AltTracebackStack; // not a fan of this one, but constructor ugly without it
    };

    /*
     * Specialized linked list stack that finds and keeps track of the top-scoring non-optimal alignments.
     * Sub-optimal alignments are represented by the locations where their traceback deviates from the optimal
     * traceback, with other steps of the traceback implicitly following the optimal path. New tracebacks can
     * be discovered while performing any of the tracebacks and added to the stack.
     */
    template <class IntType>
    class BandedGlobalAligner<IntType>::AltTracebackStack {
    public:
        AltTracebackStack(int64_t max_multi_alns, vector<BAMatrix*> sink_node_matrices);
        ~AltTracebackStack();

        /// Get the start position of the current alignment and advance deflection pointer to the first deflection
        inline void get_alignment_start(int64_t& node_id, matrix_t& matrix);

        /// Advance to the next alternate alignment
        inline void next();
        /// Are there any more alternate alignments in the stack?
        inline bool has_next();

        /// Check if a deflection from the current traceback is better than any alignments currently in the
        /// stack and if so insert it in the correct position
        inline void propose_deflection(const IntType score, const int64_t from_node_id, const int64_t row_idx,
                                       const int64_t col_idx, const int64_t to_node_id, const matrix_t to_matrix);

        /// Score of the current traceback
        inline IntType current_traceback_score();

        /// Are these the coordinates of the next deflection?
        inline bool at_next_deflection(int64_t node_id, int64_t row_idx, int64_t col_idx);

        /// Get the matrix to deflect to and advance to the next deflection
        inline BandedGlobalAligner::matrix_t deflect_to_matrix();
        /// Get the matrix and the node id to deflect to (for use at a node boundary)
        inline BandedGlobalAligner::matrix_t deflect_to_matrix(int64_t& to_node_id);

    private:
        class Deflection;

        /// Maximum number of alternate alignments to keep track of (including the optimal alignment)
        int64_t max_multi_alns;
        /// List of pairs that contain the scores of alternate alignments and the places where their
        /// traceback deviates from the optimum (Deflections). Maintains an invariant where stack is
        /// sorted in descending score order.
        list<pair<vector<Deflection>, IntType>> alt_tracebacks;

        /// Pointer to the traceback directions for the alignment we are currently tracing back
        typename list<pair<vector<Deflection>, IntType>>::iterator curr_traceback;

        /// Pointer to the next deviation from the optimal traceback we will take
        typename vector<Deflection>::iterator curr_deflxn;

        /// Internal method for propose_deflection
        inline void insert_traceback(const vector<Deflection>& traceback_prefix, const IntType score,
                                     const int64_t from_node_id, const int64_t row_idx,
                                     const int64_t col_idx, const int64_t to_node_id, const matrix_t to_matrix);
    };

    /*
     * Represents a deviation from the optimal traceback that a sub-optimal traceback takes.
     */
    template <class IntType>
    class BandedGlobalAligner<IntType>::AltTracebackStack::Deflection {
    public:
        Deflection(const int64_t from_node_id, const int64_t row_idx, const int64_t col_idx,
                   const int64_t to_node_id, const matrix_t to_matrix);
        ~Deflection();

        /// Node ID where deflection occurs
        const int64_t from_node_id;
        /// Coordinate from rectangularized band (not original matrix) where deflection occurs
        const int64_t row_idx;
        /// Coordinate from rectangularized band (not original matrix) where deflection occurs
        const int64_t col_idx;
        /// Node to deflect to
        const int64_t to_node_id;
        /// Dynamic programming matrix deflect to
        const matrix_t to_matrix;
    };

    /*
     * Translates a traceback path into a Path object and stores it in an Alignment object
     */
    template <class IntType>
    class BandedGlobalAligner<IntType>::BABuilder {
    private:
        Alignment& alignment;

        list<Mapping> node_mappings;
        list<Edit> mapping_edits;

        matrix_t matrix_state;
        bool matching;
        Node* current_node;
        int64_t edit_length;
        int64_t edit_read_end_idx;

        void finish_current_edit();
        void finish_current_node();

    public:
        BABuilder(Alignment& alignment);
        ~BABuilder();

        /// Add next step in traceback
        void update_state(matrix_t matrix, Node* node, int64_t read_idx, int64_t node_idx);
        /// Call after concluding traceback to finish adding edits to alignment
        void finalize_alignment();
    };

    // define aligners for allowed integer types
    template class BandedGlobalAligner<int8_t>;
    template class BandedGlobalAligner<int16_t>;
    template class BandedGlobalAligner<int32_t>;
    template class BandedGlobalAligner<int64_t>;
}

#endif /* banded_global_aligner_hpp */

<|endoftext|>


<commit_before>/**
 * \file
 * \brief SignalCatchingOperationsTestCase class implementation
 *
 * \author Copyright (C) 2015-2017 Kamil Szczygiel http://www.distortec.com http://www.freddiechopin.info
 *
 * \par License
 * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not
 * distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "SignalCatchingOperationsTestCase.hpp"

#include "distortos/distortosConfiguration.h"

#include "abortSignalHandler.hpp"

#include "distortos/DynamicThread.hpp"
#include "distortos/statistics.hpp"
#include "distortos/ThisThread.hpp"
#include "distortos/ThisThread-Signals.hpp"

#include <cerrno>

/// configuration required by first and second phase of SignalCatchingOperationsTestCase
#define SIGNAL_CATCHING_OPERATIONS_TEST_CASE_PHASE_1_2_ENABLED CONFIG_MAIN_THREAD_SIGNAL_ACTIONS >= 1 && \
		CONFIG_MAIN_THREAD_SIGNAL_ACTIONS <= 31

namespace distortos
{

namespace test
{

namespace
{

/*---------------------------------------------------------------------------------------------------------------------+
| local constants
+---------------------------------------------------------------------------------------------------------------------*/

/// size of stack for test threads, bytes
constexpr size_t testThreadStackSize {512};

#if SIGNAL_CATCHING_OPERATIONS_TEST_CASE_PHASE_1_2_ENABLED == 1

/// expected number of context switches in phase2() block involving thread: 1 - main thread is preempted by test thread
/// (main -> test), 2 - test thread terminates (test -> main)
constexpr decltype(statistics::getContextSwitchCount()) phase2ThreadContextSwitchCount {2};

#endif	// SIGNAL_CATCHING_OPERATIONS_TEST_CASE_PHASE_1_2_ENABLED == 1

/// expected number of context switches in phase3() block involving thread: 1 - main thread is preempted by test thread
/// (main -> test), 2 - test thread is preempted after lowering its own priority (test -> main), 3 - main thread blocks
/// by attempting to join() test thread (main -> test), 4 - test thread terminates (test -> main)
constexpr decltype(statistics::getContextSwitchCount()) phase3ThreadContextSwitchCount {4};

/*---------------------------------------------------------------------------------------------------------------------+
| local functions
+---------------------------------------------------------------------------------------------------------------------*/

#if SIGNAL_CATCHING_OPERATIONS_TEST_CASE_PHASE_1_2_ENABLED == 1

/**
 * \brief Phase 1 of test case.
 *
 * Tests various aspects of setting signal action, aiming for full coverage of execution paths through
 * SignalsCatcherControlBlock::setAssociation(). Following cases are tested:
 * - setting identical signal actions for multiple signal numbers must succeed as long as no more than
 * \a CONFIG_MAIN_THREAD_SIGNAL_ACTIONS different signal actions are used at the same time;
 * - setting the same signal action as the one that is currently set must always succeed;
 * - clearing signal action must always succeed;
 * - trying to change signal action for signal number must fail with EAGAIN if \a CONFIG_MAIN_THREAD_SIGNAL_ACTIONS
 * different signal actions are already used and the one that is changed is used by multiple signal numbers;
 * - trying to set new signal action for signal number must fail with EAGAIN when \a CONFIG_MAIN_THREAD_SIGNAL_ACTIONS
 * different signal actions are already used;
 *
 * \return true if test succeeded, false otherwise
 */

bool phase1()
{
	constexpr size_t mainThreadSignalActions {CONFIG_MAIN_THREAD_SIGNAL_ACTIONS};

	// the second to last iteration is used to set the same signal action as in the first iteration
	// the last iteration is used to set the same signal action as the one that is currently set
	for (size_t mask {}; mask <= mainThreadSignalActions + 1; ++mask)
		for (uint8_t signalNumber {}; signalNumber < SignalSet::Bitset{}.size(); ++signalNumber)
		{
			// last iteration? clip the value so that it is identical to the one from previous iteration
			const auto realMask = mask <= mainThreadSignalActions ? mask : mainThreadSignalActions;
			const SignalSet signalMask {((realMask + signalNumber) % mainThreadSignalActions)};
			const auto setSignalActionResult = ThisThread::Signals::setSignalAction(signalNumber,
					{abortSignalHandler, signalMask});
			if (setSignalActionResult.first != 0)
				return false;
			if (mask == 0)	// first iteration? previous signal action is equal to default SignalAction
			{
				if (setSignalActionResult.second.getHandler() != SignalAction{}.getHandler())
					return false;
			}
			else	// compare returned signal action with the expected one
			{
				const SignalSet previousSignalMask {((mask - 1 + signalNumber) % mainThreadSignalActions)};
				if (setSignalActionResult.second.getHandler() != abortSignalHandler ||
						setSignalActionResult.second.getSignalMask().getBitset() != previousSignalMask.getBitset())
					return false;
			}
		}

	constexpr uint8_t testSignalNumber {0};
	int ret;
	std::tie(ret, std::ignore) = ThisThread::Signals::setSignalAction(testSignalNumber,
			{abortSignalHandler, SignalSet{SignalSet::full}});
	if (ret != EAGAIN)
		return false;
	std::tie(ret, std::ignore) = ThisThread::Signals::setSignalAction(testSignalNumber, {});
	if (ret != 0)
		return false;
	std::tie(ret, std::ignore) = ThisThread::Signals::setSignalAction(testSignalNumber,
			{abortSignalHandler, SignalSet{SignalSet::full}});
	if (ret != EAGAIN)
		return false;

	for (uint8_t signalNumber {}; signalNumber < SignalSet::Bitset{}.size(); ++signalNumber)
	{
		std::tie(ret, std::ignore) = ThisThread::Signals::setSignalAction(signalNumber, {});
		if (ret != 0)
			return false;
	}

	return true;
}

/**
 * \brief Phase 2 of test case.
 *
 * Tests various error cases:
 * - attempt to get/set signal action for invalid signal number must fail with EINVAL;
 * - attempt to get/set signal action or to set signal mask in a thread that either has disabled reception of signals or
 * has disabled catching/handling of signals must fail with ENOTSUP;
 * - signal mask of a thread that either has disabled reception of signals or has disabled catching/handling of signals
 * must be equal to "full" SignalSet;
 *
 * \return true if test succeeded, false otherwise
 */

bool phase2()
{
	for (auto signalNumber = SignalSet::Bitset{}.size(); signalNumber <= UINT8_MAX; ++signalNumber)
	{
		int ret;
		std::tie(ret, std::ignore) = ThisThread::Signals::getSignalAction(signalNumber);
		if (ret != EINVAL)
			return false;
		std::tie(ret, std::ignore) = ThisThread::Signals::setSignalAction(signalNumber, SignalAction{});
		if (ret != EINVAL)
			return false;
	}

	auto testThreadLambda =
			[](int& getSignalActionRet, SignalSet& signalMask, int& setSignalActionRet, int& setSignalMaskRet)
			{
				std::tie(getSignalActionRet, std::ignore) = ThisThread::Signals::getSignalAction({});
				signalMask = ThisThread::Signals::getSignalMask();
				std::tie(setSignalActionRet, std::ignore) = ThisThread::Signals::setSignalAction({}, SignalAction{});
				setSignalMaskRet = ThisThread::Signals::setSignalMask(SignalSet{SignalSet::full});
			};

	{
		int getSignalActionRet {};
		SignalSet signalMask {SignalSet::empty};
		int setSignalActionRet {};
		int setSignalMaskRet {};
		auto testThread = makeAndStartDynamicThread({testThreadStackSize, false, 0, 0, UINT8_MAX}, testThreadLambda,
				std::ref(getSignalActionRet), std::ref(signalMask), std::ref(setSignalActionRet),
				std::ref(setSignalMaskRet));
		testThread.join();
		if (getSignalActionRet != ENOTSUP)
			return false;
		if (signalMask.getBitset() != SignalSet{SignalSet::full}.getBitset())
			return false;
		if (setSignalActionRet != ENOTSUP)
			return false;
		if (setSignalMaskRet != ENOTSUP)
			return false;
	}

	{
		int getSignalActionRet {};
		SignalSet signalMask {SignalSet::empty};
		int setSignalActionRet {};
		int setSignalMaskRet {};
		auto testThread = makeAndStartDynamicThread({testThreadStackSize, true, 0, 0, UINT8_MAX}, testThreadLambda,
				std::ref(getSignalActionRet), std::ref(signalMask), std::ref(setSignalActionRet),
				std::ref(setSignalMaskRet));
		testThread.join();
		if (getSignalActionRet != ENOTSUP)
			return false;
		if (signalMask.getBitset() != SignalSet{SignalSet::full}.getBitset())
			return false;
		if (setSignalActionRet != ENOTSUP)
			return false;
		if (setSignalMaskRet != ENOTSUP)
			return false;
	}

	return true;
}

#endif	// SIGNAL_CATCHING_OPERATIONS_TEST_CASE_PHASE_1_2_ENABLED == 1

/**
 * \brief Phase 3 of test case.
 *
 * Tests whether generation/queuing of signal fails with ENOSPC if the amount of target thread's free stack is too small
 * to request signal delivery.
 *
 * \return true if test succeeded, false otherwise
 */

bool phase3()
{
	static_assert(SignalCatchingOperationsTestCase::getTestCasePriority() < UINT8_MAX &&
			SignalCatchingOperationsTestCase::getTestCasePriority() > 1, "Invalid test case priority");

	constexpr uint8_t testSignalNumber {16};
	const auto testThreadLambda = []()
	{
		ThisThread::Signals::setSignalAction(testSignalNumber, {abortSignalHandler, SignalSet{SignalSet::empty}});
		// This non-inline "inner" lambda with big array is meant to ensure that the biggest stack usage is when the
		// context is switched, leaving no room to request signal delivery.
		[]() __attribute__ ((noinline))
		{
			volatile uint8_t array[testThreadStackSize / 2] {};
			ThisThread::setPriority(1);
			(void)array[0];	// make sure the array is not removed and prevent tail-call optimization
		}();
	};

	size_t stackSize;
	{
		auto testThread = makeAndStartDynamicThread({testThreadStackSize, true, 1, 1, UINT8_MAX}, testThreadLambda);
		testThread.join();
		stackSize = testThread.getStackHighWaterMark();
	}
	{
		auto testThread = makeAndStartDynamicThread({stackSize, true, 1, 1, UINT8_MAX}, testThreadLambda);
		const auto ret1 = testThread.generateSignal(testSignalNumber);
		const auto ret2 = testThread.queueSignal(testSignalNumber, sigval{});
		testThread.join();
		if (ret1 != ENOSPC || ret2 != ENOSPC)	// signal generation/queuing must fail with ENOSPC
			return false;
		if (testThread.getPendingSignalSet().getBitset().none() == false)	// no signals may be pending
			return false;
	}

	return true;
}

}	// namespace

/*---------------------------------------------------------------------------------------------------------------------+
| private functions
+---------------------------------------------------------------------------------------------------------------------*/

bool SignalCatchingOperationsTestCase::run_() const
{
	const auto contextSwitchCount = statistics::getContextSwitchCount();

	constexpr auto phase3ExpectedContextSwitchCount = 2 * phase3ThreadContextSwitchCount;

#if SIGNAL_CATCHING_OPERATIONS_TEST_CASE_PHASE_1_2_ENABLED == 1
	constexpr auto phase2ExpectedContextSwitchCount = 2 * phase2ThreadContextSwitchCount;
	constexpr auto expectedContextSwitchCount = phase2ExpectedContextSwitchCount + phase3ExpectedContextSwitchCount;
	for (const auto& function : {phase1, phase2, phase3})
#else	// SIGNAL_CATCHING_OPERATIONS_TEST_CASE_PHASE_1_2_ENABLED != 1
	constexpr auto expectedContextSwitchCount = phase3ExpectedContextSwitchCount;
	for (const auto& function : {phase3})
#endif	// SIGNAL_CATCHING_OPERATIONS_TEST_CASE_PHASE_1_2_ENABLED != 1
	{
		// initially no signals may be pending
		if (ThisThread::Signals::getPendingSignalSet().getBitset().none() == false)
			return false;

		if (function() == false)
			return false;
	}

	// after the test no signals may be pending
	if (ThisThread::Signals::getPendingSignalSet().getBitset().none() == false)
		return false;

	if (statistics::getContextSwitchCount() - contextSwitchCount != expectedContextSwitchCount)
		return false;

	return true;
}

}	// namespace test

}	// namespace distortos
<commit_msg>Disable SignalCatchingOperationsTestCase if signals are disabled<commit_after>/**
 * \file
 * \brief SignalCatchingOperationsTestCase class implementation
 *
 * \author Copyright (C) 2015-2017 Kamil Szczygiel http://www.distortec.com http://www.freddiechopin.info
 *
 * \par License
 * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not
 * distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "SignalCatchingOperationsTestCase.hpp"

#include "distortos/distortosConfiguration.h"

#if CONFIG_SIGNALS_ENABLE == 1

#include "abortSignalHandler.hpp"

#include "distortos/DynamicThread.hpp"
#include "distortos/statistics.hpp"
#include "distortos/ThisThread.hpp"
#include "distortos/ThisThread-Signals.hpp"

#include <cerrno>

/// configuration required by first and second phase of SignalCatchingOperationsTestCase
#define SIGNAL_CATCHING_OPERATIONS_TEST_CASE_PHASE_1_2_ENABLED CONFIG_MAIN_THREAD_SIGNAL_ACTIONS >= 1 && \
		CONFIG_MAIN_THREAD_SIGNAL_ACTIONS <= 31

#endif	// CONFIG_SIGNALS_ENABLE == 1

namespace distortos
{

namespace test
{

#if CONFIG_SIGNALS_ENABLE == 1

namespace
{

/*---------------------------------------------------------------------------------------------------------------------+
| local constants
+---------------------------------------------------------------------------------------------------------------------*/

/// size of stack for test threads, bytes
constexpr size_t testThreadStackSize {512};

#if SIGNAL_CATCHING_OPERATIONS_TEST_CASE_PHASE_1_2_ENABLED == 1

/// expected number of context switches in phase2() block involving thread: 1 - main thread is preempted by test thread
/// (main -> test), 2 - test thread terminates (test -> main)
constexpr decltype(statistics::getContextSwitchCount()) phase2ThreadContextSwitchCount {2};

#endif	// SIGNAL_CATCHING_OPERATIONS_TEST_CASE_PHASE_1_2_ENABLED == 1

/// expected number of context switches in phase3() block involving thread: 1 - main thread is preempted by test thread
/// (main -> test), 2 - test thread is preempted after lowering its own priority (test -> main), 3 - main thread blocks
/// by attempting to join() test thread (main -> test), 4 - test thread terminates (test -> main)
constexpr decltype(statistics::getContextSwitchCount()) phase3ThreadContextSwitchCount {4};

/*---------------------------------------------------------------------------------------------------------------------+
| local functions
+---------------------------------------------------------------------------------------------------------------------*/

#if SIGNAL_CATCHING_OPERATIONS_TEST_CASE_PHASE_1_2_ENABLED == 1

/**
 * \brief Phase 1 of test case.
 *
 * Tests various aspects of setting signal action, aiming for full coverage of execution paths through
 * SignalsCatcherControlBlock::setAssociation(). Following cases are tested:
 * - setting identical signal actions for multiple signal numbers must succeed as long as no more than
 * \a CONFIG_MAIN_THREAD_SIGNAL_ACTIONS different signal actions are used at the same time;
 * - setting the same signal action as the one that is currently set must always succeed;
 * - clearing signal action must always succeed;
 * - trying to change signal action for signal number must fail with EAGAIN if \a CONFIG_MAIN_THREAD_SIGNAL_ACTIONS
 * different signal actions are already used and the one that is changed is used by multiple signal numbers;
 * - trying to set new signal action for signal number must fail with EAGAIN when \a CONFIG_MAIN_THREAD_SIGNAL_ACTIONS
 * different signal actions are already used;
 *
 * \return true if test succeeded, false otherwise
 */

bool phase1()
{
	constexpr size_t mainThreadSignalActions {CONFIG_MAIN_THREAD_SIGNAL_ACTIONS};

	// the second to last iteration is used to set the same signal action as in the first iteration
	// the last iteration is used to set the same signal action as the one that is currently set
	for (size_t mask {}; mask <= mainThreadSignalActions + 1; ++mask)
		for (uint8_t signalNumber {}; signalNumber < SignalSet::Bitset{}.size(); ++signalNumber)
		{
			// last iteration? clip the value so that it is identical to the one from previous iteration
			const auto realMask = mask <= mainThreadSignalActions ? mask : mainThreadSignalActions;
			const SignalSet signalMask {((realMask + signalNumber) % mainThreadSignalActions)};
			const auto setSignalActionResult = ThisThread::Signals::setSignalAction(signalNumber,
					{abortSignalHandler, signalMask});
			if (setSignalActionResult.first != 0)
				return false;
			if (mask == 0)	// first iteration? previous signal action is equal to default SignalAction
			{
				if (setSignalActionResult.second.getHandler() != SignalAction{}.getHandler())
					return false;
			}
			else	// compare returned signal action with the expected one
			{
				const SignalSet previousSignalMask {((mask - 1 + signalNumber) % mainThreadSignalActions)};
				if (setSignalActionResult.second.getHandler() != abortSignalHandler ||
						setSignalActionResult.second.getSignalMask().getBitset() != previousSignalMask.getBitset())
					return false;
			}
		}

	constexpr uint8_t testSignalNumber {0};
	int ret;
	std::tie(ret, std::ignore) = ThisThread::Signals::setSignalAction(testSignalNumber,
			{abortSignalHandler, SignalSet{SignalSet::full}});
	if (ret != EAGAIN)
		return false;
	std::tie(ret, std::ignore) = ThisThread::Signals::setSignalAction(testSignalNumber, {});
	if (ret != 0)
		return false;
	std::tie(ret, std::ignore) = ThisThread::Signals::setSignalAction(testSignalNumber,
			{abortSignalHandler, SignalSet{SignalSet::full}});
	if (ret != EAGAIN)
		return false;

	for (uint8_t signalNumber {}; signalNumber < SignalSet::Bitset{}.size(); ++signalNumber)
	{
		std::tie(ret, std::ignore) = ThisThread::Signals::setSignalAction(signalNumber, {});
		if (ret != 0)
			return false;
	}

	return true;
}

/**
 * \brief Phase 2 of test case.
 *
 * Tests various error cases:
 * - attempt to get/set signal action for invalid signal number must fail with EINVAL;
 * - attempt to get/set signal action or to set signal mask in a thread that either has disabled reception of signals or
 * has disabled catching/handling of signals must fail with ENOTSUP;
 * - signal mask of a thread that either has disabled reception of signals or has disabled catching/handling of signals
 * must be equal to "full" SignalSet;
 *
 * \return true if test succeeded, false otherwise
 */

bool phase2()
{
	for (auto signalNumber = SignalSet::Bitset{}.size(); signalNumber <= UINT8_MAX; ++signalNumber)
	{
		int ret;
		std::tie(ret, std::ignore) = ThisThread::Signals::getSignalAction(signalNumber);
		if (ret != EINVAL)
			return false;
		std::tie(ret, std::ignore) = ThisThread::Signals::setSignalAction(signalNumber, SignalAction{});
		if (ret != EINVAL)
			return false;
	}

	auto testThreadLambda =
			[](int& getSignalActionRet, SignalSet& signalMask, int& setSignalActionRet, int& setSignalMaskRet)
			{
				std::tie(getSignalActionRet, std::ignore) = ThisThread::Signals::getSignalAction({});
				signalMask = ThisThread::Signals::getSignalMask();
				std::tie(setSignalActionRet, std::ignore) = ThisThread::Signals::setSignalAction({}, SignalAction{});
				setSignalMaskRet = ThisThread::Signals::setSignalMask(SignalSet{SignalSet::full});
			};

	{
		int getSignalActionRet {};
		SignalSet signalMask {SignalSet::empty};
		int setSignalActionRet {};
		int setSignalMaskRet {};
		auto testThread = makeAndStartDynamicThread({testThreadStackSize, false, 0, 0, UINT8_MAX}, testThreadLambda,
				std::ref(getSignalActionRet), std::ref(signalMask), std::ref(setSignalActionRet),
				std::ref(setSignalMaskRet));
		testThread.join();
		if (getSignalActionRet != ENOTSUP)
			return false;
		if (signalMask.getBitset() != SignalSet{SignalSet::full}.getBitset())
			return false;
		if (setSignalActionRet != ENOTSUP)
			return false;
		if (setSignalMaskRet != ENOTSUP)
			return false;
	}

	{
		int getSignalActionRet {};
		SignalSet signalMask {SignalSet::empty};
		int setSignalActionRet {};
		int setSignalMaskRet {};
		auto testThread = makeAndStartDynamicThread({testThreadStackSize, true, 0, 0, UINT8_MAX}, testThreadLambda,
				std::ref(getSignalActionRet), std::ref(signalMask), std::ref(setSignalActionRet),
				std::ref(setSignalMaskRet));
		testThread.join();
		if (getSignalActionRet != ENOTSUP)
			return false;
		if (signalMask.getBitset() != SignalSet{SignalSet::full}.getBitset())
			return false;
		if (setSignalActionRet != ENOTSUP)
			return false;
		if (setSignalMaskRet != ENOTSUP)
			return false;
	}

	return true;
}

#endif	// SIGNAL_CATCHING_OPERATIONS_TEST_CASE_PHASE_1_2_ENABLED == 1

/**
 * \brief Phase 3 of test case.
 *
 * Tests whether generation/queuing of signal fails with ENOSPC if the amount of target thread's free stack is too small
 * to request signal delivery.
 *
 * \return true if test succeeded, false otherwise
 */

bool phase3()
{
	static_assert(SignalCatchingOperationsTestCase::getTestCasePriority() < UINT8_MAX &&
			SignalCatchingOperationsTestCase::getTestCasePriority() > 1, "Invalid test case priority");

	constexpr uint8_t testSignalNumber {16};
	const auto testThreadLambda = []()
	{
		ThisThread::Signals::setSignalAction(testSignalNumber, {abortSignalHandler, SignalSet{SignalSet::empty}});
		// This non-inline "inner" lambda with big array is meant to ensure that the biggest stack usage is when the
		// context is switched, leaving no room to request signal delivery.
		[]() __attribute__ ((noinline))
		{
			volatile uint8_t array[testThreadStackSize / 2] {};
			ThisThread::setPriority(1);
			(void)array[0];	// make sure the array is not removed and prevent tail-call optimization
		}();
	};

	size_t stackSize;
	{
		auto testThread = makeAndStartDynamicThread({testThreadStackSize, true, 1, 1, UINT8_MAX}, testThreadLambda);
		testThread.join();
		stackSize = testThread.getStackHighWaterMark();
	}
	{
		auto testThread = makeAndStartDynamicThread({stackSize, true, 1, 1, UINT8_MAX}, testThreadLambda);
		const auto ret1 = testThread.generateSignal(testSignalNumber);
		const auto ret2 = testThread.queueSignal(testSignalNumber, sigval{});
		testThread.join();
		if (ret1 != ENOSPC || ret2 != ENOSPC)	// signal generation/queuing must fail with ENOSPC
			return false;
		if (testThread.getPendingSignalSet().getBitset().none() == false)	// no signals may be pending
			return false;
	}

	return true;
}

}	// namespace

#endif	// CONFIG_SIGNALS_ENABLE == 1

/*---------------------------------------------------------------------------------------------------------------------+
| private functions
+---------------------------------------------------------------------------------------------------------------------*/

bool SignalCatchingOperationsTestCase::run_() const
{
#if CONFIG_SIGNALS_ENABLE == 1

	const auto contextSwitchCount = statistics::getContextSwitchCount();

	constexpr auto phase3ExpectedContextSwitchCount = 2 * phase3ThreadContextSwitchCount;

#if SIGNAL_CATCHING_OPERATIONS_TEST_CASE_PHASE_1_2_ENABLED == 1
	constexpr auto phase2ExpectedContextSwitchCount = 2 * phase2ThreadContextSwitchCount;
	constexpr auto expectedContextSwitchCount = phase2ExpectedContextSwitchCount + phase3ExpectedContextSwitchCount;
	for (const auto& function : {phase1, phase2, phase3})
#else	// SIGNAL_CATCHING_OPERATIONS_TEST_CASE_PHASE_1_2_ENABLED != 1
	constexpr auto expectedContextSwitchCount = phase3ExpectedContextSwitchCount;
	for (const auto& function : {phase3})
#endif	// SIGNAL_CATCHING_OPERATIONS_TEST_CASE_PHASE_1_2_ENABLED != 1
	{
		// initially no signals may be pending
		if (ThisThread::Signals::getPendingSignalSet().getBitset().none() == false)
			return false;

		if (function() == false)
			return false;
	}

	// after the test no signals may be pending
	if (ThisThread::Signals::getPendingSignalSet().getBitset().none() == false)
		return false;

	if (statistics::getContextSwitchCount() - contextSwitchCount != expectedContextSwitchCount)
		return false;

#endif	// CONFIG_SIGNALS_ENABLE == 1

	return true;
}

}	// namespace test

}	// namespace distortos
<|endoftext|>


<commit_before>//
// Copyright (c) 2014 Caitlin Potter and Contributors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//  * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//  * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//

#include <TimedText/WebVTTParser.h>
#include <TimedText/SynchronousBuffer.h>
#include <gtest/gtest.h>
using namespace TimedText;

void testParseHeader(const char *text, bool shouldFinal,
                     bool shouldParse,
                     WebVTTParser::Status shouldStatus)
{
  static int run = 0;
  SynchronousBuffer buffer;
  WebVTTParser parser(buffer);
  ++run;
  EXPECT_TRUE(buffer.refill(text, -1, shouldFinal))
    << "in testParseHeader #" << run;
  EXPECT_EQ(shouldParse, parser.parseHeader())
    << "in testParseHeader #" << run << " when trying to parse \"" << text << "\"";
  EXPECT_EQ(parser.status, shouldStatus)
    << "in testParseHeader #" << run;
}

TEST(SynchronousWebVTTParser, ParseHeader)
{
  testParseHeader("WEBVTT", true, true, WebVTTParser::Finished);
  testParseHeader("WEBVTT\n", true, true, WebVTTParser::Finished);
  testParseHeader("WEBVTT\r", true, true, WebVTTParser::Finished);
  testParseHeader("WEBVTT wutnow", true, true, WebVTTParser::Finished);
  testParseHeader("WEBVTT\tohai", true, true, WebVTTParser::Finished);
  testParseHeader("WEBVTTT", true, false, WebVTTParser::Aborted);
  testParseHeader("\xEF\xBB\xBFWEBVTT", true, true, WebVTTParser::Finished);
  testParseHeader("\xEF\xBB\xBFWEBVTT\n", true, true, WebVTTParser::Finished);
  testParseHeader("\xEF\xBB\xBFWEBVTT\r", true, true, WebVTTParser::Finished);
  testParseHeader("\xEF\xBB\xBFWEBVTT ", true, true, WebVTTParser::Finished);
  testParseHeader("\xEF\xBB\xBFWEBVTT\t", true, true, WebVTTParser::Finished);
  testParseHeader("\xEF\xBB\xBFWEBVTTT", true, false, WebVTTParser::Aborted);
  testParseHeader("\xBB\xBFWEBVTT", true, false, WebVTTParser::Aborted);
  testParseHeader("\xBFWEBVTT", true, false, WebVTTParser::Aborted);
  testParseHeader("\xEF\xBBWEBVTT", true, false, WebVTTParser::Aborted);
  testParseHeader("\xEF\xBB\xBFPEBVTT", true, false, WebVTTParser::Aborted);
}

void testCollectTimingsAndSettings(const char *text, bool isOk,
                                   double expectedStartTime,
                                   double expectedEndTime,
                                   const char *expectedSettings = "")
{
  static int run = 0;
  SynchronousBuffer buffer;
  WebVTTParser parser(buffer);
  WebVTTParser::ParseState expectedState = isOk ? WebVTTParser::CueText
                                                : WebVTTParser::BadCue;
  String line;
  ++run;
  EXPECT_TRUE(buffer.refill(text, -1, true));
  EXPECT_TRUE(buffer.getline(line));
  EXPECT_EQ(expectedState, parser.collectTimingsAndSettings(line))
    << "in testCollectTimingsAndSettings #" << run;
  EXPECT_EQ(expectedStartTime,parser.currentStartTime.toSeconds())
    << "in testCollectTimingsAndSettings #" << run;
  EXPECT_EQ(expectedEndTime,parser.currentEndTime.toSeconds())
    << "in testCollectTimingsAndSettings #" << run;
  EXPECT_STREQ(expectedSettings,parser.currentSettings.text())
    << "in testCollectTimingsAndSettings #" << run;
}

TEST(SynchronousWebVTTParser, CollectTimingsAndSettings)
{
  // Different ways that timestamps can be successfully parsed
  testCollectTimingsAndSettings("01:00:35.555-->02:00:35.666", true, 3635.555, 7235.666);
  testCollectTimingsAndSettings("1:00:35.555-->2:00:35.666", true, 3635.555, 7235.666);
  testCollectTimingsAndSettings("00:35.555-->00:35.666", true, 35.555, 35.666);
  testCollectTimingsAndSettings("00:35.555 --> 00:35.666", true, 35.555, 35.666);
  testCollectTimingsAndSettings("00:35.555\t-->\t00:35.666", true, 35.555, 35.666);
  testCollectTimingsAndSettings("00:35.555\f-->\f00:35.666", true, 35.555, 35.666);
  testCollectTimingsAndSettings("00:35.555\f\f-->\f\f00:35.666", true, 35.555, 35.666);
  testCollectTimingsAndSettings("00:35.555\t\t-->\t\t00:35.666", true, 35.555, 35.666);
  testCollectTimingsAndSettings("00:35.555  -->  00:35.666", true, 35.555, 35.666);

  // Different syntax errors
  testCollectTimingsAndSettings("01:0:35.555-->02:00:35.666", false, -1, -1);
  testCollectTimingsAndSettings("1:00:35.555-->2:0:35.666", false, 3635.555, -1);
  testCollectTimingsAndSettings("00:5.555-->00:35.666", false, -1, -1);
  testCollectTimingsAndSettings("00:02.555 --> 00:5.666", false, 2.555, -1);
  testCollectTimingsAndSettings("00:35.55\t-->\t00:35.066", false, -1, -1);
  testCollectTimingsAndSettings("00:35.055\f-->\f00:35.66", false, 35.055, -1);
  testCollectTimingsAndSettings(":35.555\f\f-->\f\f00:35.666", false, -1, -1);
  testCollectTimingsAndSettings("00:35.555\t\t-->\t\t:35.666", false, 35.555, -1);
  testCollectTimingsAndSettings("00:35.555  --->  00:35.666", false, 35.555, -1);
  testCollectTimingsAndSettings("00:35.555  -->>  00:35.666", false, 35.555, -1);
}
<commit_msg>Fix failing CollectTimingsAndSettings tests<commit_after>//
// Copyright (c) 2014 Caitlin Potter and Contributors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//  * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//  * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//

#include <TimedText/WebVTTParser.h>
#include <TimedText/SynchronousBuffer.h>
#include <gtest/gtest.h>
using namespace TimedText;

void testParseHeader(const char *text, bool shouldFinal,
                     bool shouldParse,
                     WebVTTParser::Status shouldStatus)
{
  static int run = 0;
  SynchronousBuffer buffer;
  WebVTTParser parser(buffer);
  ++run;
  EXPECT_TRUE(buffer.refill(text, -1, shouldFinal))
    << "in testParseHeader #" << run;
  EXPECT_EQ(shouldParse, parser.parseHeader())
    << "in testParseHeader #" << run << " when trying to parse \"" << text << "\"";
  EXPECT_EQ(parser.status, shouldStatus)
    << "in testParseHeader #" << run;
}

TEST(SynchronousWebVTTParser, ParseHeader)
{
  testParseHeader("WEBVTT", true, true, WebVTTParser::Finished);
  testParseHeader("WEBVTT\n", true, true, WebVTTParser::Finished);
  testParseHeader("WEBVTT\r", true, true, WebVTTParser::Finished);
  testParseHeader("WEBVTT wutnow", true, true, WebVTTParser::Finished);
  testParseHeader("WEBVTT\tohai", true, true, WebVTTParser::Finished);
  testParseHeader("WEBVTTT", true, false, WebVTTParser::Aborted);
  testParseHeader("\xEF\xBB\xBFWEBVTT", true, true, WebVTTParser::Finished);
  testParseHeader("\xEF\xBB\xBFWEBVTT\n", true, true, WebVTTParser::Finished);
  testParseHeader("\xEF\xBB\xBFWEBVTT\r", true, true, WebVTTParser::Finished);
  testParseHeader("\xEF\xBB\xBFWEBVTT ", true, true, WebVTTParser::Finished);
  testParseHeader("\xEF\xBB\xBFWEBVTT\t", true, true, WebVTTParser::Finished);
  testParseHeader("\xEF\xBB\xBFWEBVTTT", true, false, WebVTTParser::Aborted);
  testParseHeader("\xBB\xBFWEBVTT", true, false, WebVTTParser::Aborted);
  testParseHeader("\xBFWEBVTT", true, false, WebVTTParser::Aborted);
  testParseHeader("\xEF\xBBWEBVTT", true, false, WebVTTParser::Aborted);
  testParseHeader("\xEF\xBB\xBFPEBVTT", true, false, WebVTTParser::Aborted);
}

void testCollectTimingsAndSettings(const char *text, bool isOk,
                                   double expectedStartTime,
                                   double expectedEndTime,
                                   const char *expectedSettings = "")
{
  static int run = 0;
  SynchronousBuffer buffer;
  WebVTTParser parser(buffer);
  WebVTTParser::ParseState expectedState = isOk ? WebVTTParser::CueText
                                                : WebVTTParser::BadCue;
  String line;
  ++run;
  // The MalformedTimestamp value is -1 milliseconds, but it's simpler to
  // write -1.
  if(expectedStartTime < 0)
    expectedStartTime = -0.001;
  if(expectedEndTime < 0)
    expectedEndTime = -0.001;
  EXPECT_TRUE(buffer.refill(text, -1, true));
  EXPECT_TRUE(buffer.getline(line));
  EXPECT_EQ(expectedState, parser.collectTimingsAndSettings(line))
    << "in testCollectTimingsAndSettings #" << run;
  EXPECT_EQ(expectedStartTime,parser.currentStartTime.toSeconds())
    << "in testCollectTimingsAndSettings #" << run;
  EXPECT_EQ(expectedEndTime,parser.currentEndTime.toSeconds())
    << "in testCollectTimingsAndSettings #" << run;
  EXPECT_STREQ(expectedSettings,parser.currentSettings.text())
    << "in testCollectTimingsAndSettings #" << run;
}

TEST(SynchronousWebVTTParser, CollectTimingsAndSettings)
{
  // Different ways that timestamps can be successfully parsed
  testCollectTimingsAndSettings("01:00:35.555-->02:00:35.666", true, 3635.555, 7235.666);
  testCollectTimingsAndSettings("1:00:35.555-->2:00:35.666", true, 3635.555, 7235.666);
  testCollectTimingsAndSettings("00:35.555-->00:35.666", true, 35.555, 35.666);
  testCollectTimingsAndSettings("00:35.555 --> 00:35.666", true, 35.555, 35.666);
  testCollectTimingsAndSettings("00:35.555\t-->\t00:35.666", true, 35.555, 35.666);
  testCollectTimingsAndSettings("00:35.555\f-->\f00:35.666", true, 35.555, 35.666);
  testCollectTimingsAndSettings("00:35.555\f\f-->\f\f00:35.666", true, 35.555, 35.666);
  testCollectTimingsAndSettings("00:35.555\t\t-->\t\t00:35.666", true, 35.555, 35.666);
  testCollectTimingsAndSettings("00:35.555  -->  00:35.666", true, 35.555, 35.666);

  // Different syntax errors
  testCollectTimingsAndSettings("01:0:35.555-->02:00:35.666", false, -1, -1);
  testCollectTimingsAndSettings("1:00:35.555-->2:0:35.666", false, 3635.555, -1);
  testCollectTimingsAndSettings("00:5.555-->00:35.666", false, -1, -1);
  testCollectTimingsAndSettings("00:02.555 --> 00:5.666", false, 2.555, -1);
  testCollectTimingsAndSettings("00:35.55\t-->\t00:35.066", false, -1, -1);
  testCollectTimingsAndSettings("00:35.055\f-->\f00:35.66", false, 35.055, -1);
  testCollectTimingsAndSettings(":35.555\f\f-->\f\f00:35.666", false, -1, -1);
  testCollectTimingsAndSettings("00:35.555\t\t-->\t\t:35.666", false, 35.555, -1);
  testCollectTimingsAndSettings("00:35.555  --->  00:35.666", false, 35.555, -1);
  testCollectTimingsAndSettings("00:35.555  -->>  00:35.666", false, 35.555, -1);
}
<|endoftext|>


<commit_before>#include <cstdio>
#include <algorithm>
#include <limits>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>

#include "server.h"
#include "stinger_net/stinger_server_state.h"

extern "C" {
#include "stinger_core/stinger.h"
#include "stinger_core/stinger_shared.h"
#include "stinger_core/xmalloc.h"
#include "stinger_utils/stinger_utils.h"
#include "stinger_utils/timer.h"
#include "stinger_utils/dimacs_support.h"
#include "stinger_utils/json_support.h"
#include "stinger_utils/csv.h"
}

using namespace gt::stinger;

int main(int argc, char *argv[])
{
  StingerServerState & server_state = StingerServerState::get_server_state();

  /* default global options */
  int port_names = 10101;
  int port_streams = port_names + 1;
  int port_algs = port_names + 2;
  int unleash_daemon = 0;

  char * graph_name = (char *) xmalloc (128*sizeof(char));
  sprintf(graph_name, "/default");
  char * input_file = (char *) xmalloc (1024*sizeof(char));
  input_file[0] = '\0';
  char * file_type = (char *) xmalloc (128*sizeof(char));
  file_type[0] = '\0';
  int use_numerics = 0;

  /* parse command line configuration */
  int opt = 0;
  while(-1 != (opt = getopt(argc, argv, "a:s:p:b:n:i:t:1h?dkvc:f:"))) {
    switch(opt) {
      case 'p': {
		  port_names = atoi(optarg);
		} break;

      case 'a': {
		  port_algs = atoi(optarg);
		} break;
      case 's': {
		  port_streams = atoi(optarg);
		} break;

      case 'n': {
		  strcpy (graph_name, optarg);
		} break;

      case 'k': {
		  server_state.set_write_alg_data(true);
		} break;

      case 'v': {
		  server_state.set_write_names(true);
		} break;

      case 'c': {
		  server_state.set_history_cap(atol(optarg));
		} break;

      case 'f': {
		  server_state.set_out_dir(optarg);
		} break;

      case 'i': {
		  strcpy (input_file, optarg);
		} break;

      case 't': {
		  strcpy (file_type, optarg);
		} break;

      case '1': {
		  use_numerics = 1;
		} break;
      case 'd': {
		  unleash_daemon = 1;
		} break;

      case '?':
      case 'h': {
		  printf("Usage:    %s\n"
		         "   [-p port_names]\n"
			 "   [-a port_algs]\n"
			 "   [-s port_streams]\n"
			 "   [-n graph_name]\n"
			 "   [-i input_file_path]\n"
			 "   [-t file_type]\n"
			 "   [-1 (for numeric IDs)]\n"
			 "   [-d]\n"
			 "   [-k write algorithm states to disk]\n"
			 "   [-v write vertex name mapping to disk]\n"
			 "   [-f output directory for vertex names, alg states]\n"
			 "   [-c cap number of history files to keep per alg]  \n", argv[0]);
		  printf("Defaults:\n\tport_names: %d\n\tport_algs: %d\n\tport_streams: %d\n\tgraph_name: %s\n", port_names, port_algs, port_streams, graph_name);
		  printf("-d: daemon mode\n");
		  exit(0);
		} break;

    }
  }

  /* print configuration to the terminal */
  printf("\tName: %s\n", graph_name);

  /* allocate the graph */
  struct stinger * S = stinger_shared_new(&graph_name);
  //struct stinger * S = stinger_new ();
  size_t graph_sz = S->length + sizeof(struct stinger);

  /* load edges from disk (if applicable) */
  if (input_file[0] != '\0')
  {
    switch (file_type[0])
    {
      case 'b': {
		  int64_t nv, ne;
		  int64_t *off, *ind, *weight, *graphmem;
		  snarf_graph (input_file, &nv, &ne, (int64_t**)&off,
		      (int64_t**)&ind, (int64_t**)&weight, (int64_t**)&graphmem);
		  stinger_set_initial_edges (S, nv, 0, off, ind, weight, NULL, NULL, 0);
		  free(graphmem);
		} break;  /* STINGER binary */

      case 'c': {
		  load_csv_graph (S, input_file, use_numerics);
		} break;  /* CSV */

      case 'd': {
		  load_dimacs_graph (S, input_file);
		} break;  /* DIMACS */

      case 'g': {
		} break;  /* GML / GraphML / GEXF -- you pick */

      case 'j': {
		  load_json_graph (S, input_file);
		} break;  /* JSON */

      case 'x': {
		} break;  /* XML */

      default:	{
		  printf("Unsupported file type.\n");
		  exit(0);
		} break;
    }
  }

  printf("Graph created. Running stats...");
  tic();
  printf("\n\tVertices: %ld\n\tEdges: %ld\n",
      stinger_num_active_vertices(S), stinger_total_edges(S));

  /* consistency check */
  printf("\tConsistency %ld\n", (long) stinger_consistency_check(S, S->max_nv));
  printf("\tDone. %lf seconds\n", toc());

  /* we need a socket that can reply with the shmem name & size of the graph */
  pid_t name_pid, batch_pid;

  /* child will handle name and size requests */
  name_pid = fork ();
  if (name_pid == 0)
  {
    start_udp_graph_name_server (graph_name, graph_sz, port_names);
    exit (0);
  }

  /* and we need a listener that can receive new edges */
  batch_pid = fork ();
  if (batch_pid == 0)
  {
    start_tcp_batch_server (S, graph_name, port_streams, port_algs);
    exit (0);
  }

  if(unleash_daemon) {
    while(1) { sleep(10); }
  } else {
    printf("Press <q> to shut down the server...\n");
    while (getchar() != 'q');
  }

  printf("Shutting down the name server..."); fflush(stdout);
  int status;
  kill(name_pid, SIGTERM);
  waitpid(name_pid, &status, 0);
  printf(" done.\n"); fflush(stdout);

  printf("Shutting down the batch server..."); fflush(stdout);
  kill(batch_pid, SIGTERM);
  waitpid(batch_pid, &status, 0);
  printf(" done.\n"); fflush(stdout);

  /* clean up */
  stinger_shared_free(S, graph_name, graph_sz);
  free(graph_name);
  free(input_file);

  return 0;
}
<commit_msg>Making daemon mode in the server usage screen a bit clearer.<commit_after>#include <cstdio>
#include <algorithm>
#include <limits>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>

#include "server.h"
#include "stinger_net/stinger_server_state.h"

extern "C" {
#include "stinger_core/stinger.h"
#include "stinger_core/stinger_shared.h"
#include "stinger_core/xmalloc.h"
#include "stinger_utils/stinger_utils.h"
#include "stinger_utils/timer.h"
#include "stinger_utils/dimacs_support.h"
#include "stinger_utils/json_support.h"
#include "stinger_utils/csv.h"
}

using namespace gt::stinger;

int main(int argc, char *argv[])
{
  StingerServerState & server_state = StingerServerState::get_server_state();

  /* default global options */
  int port_names = 10101;
  int port_streams = port_names + 1;
  int port_algs = port_names + 2;
  int unleash_daemon = 0;

  char * graph_name = (char *) xmalloc (128*sizeof(char));
  sprintf(graph_name, "/default");
  char * input_file = (char *) xmalloc (1024*sizeof(char));
  input_file[0] = '\0';
  char * file_type = (char *) xmalloc (128*sizeof(char));
  file_type[0] = '\0';
  int use_numerics = 0;

  /* parse command line configuration */
  int opt = 0;
  while(-1 != (opt = getopt(argc, argv, "a:s:p:b:n:i:t:1h?dkvc:f:"))) {
    switch(opt) {
      case 'p': {
		  port_names = atoi(optarg);
		} break;

      case 'a': {
		  port_algs = atoi(optarg);
		} break;
      case 's': {
		  port_streams = atoi(optarg);
		} break;

      case 'n': {
		  strcpy (graph_name, optarg);
		} break;

      case 'k': {
		  server_state.set_write_alg_data(true);
		} break;

      case 'v': {
		  server_state.set_write_names(true);
		} break;

      case 'c': {
		  server_state.set_history_cap(atol(optarg));
		} break;

      case 'f': {
		  server_state.set_out_dir(optarg);
		} break;

      case 'i': {
		  strcpy (input_file, optarg);
		} break;

      case 't': {
		  strcpy (file_type, optarg);
		} break;

      case '1': {
		  use_numerics = 1;
		} break;
      case 'd': {
		  unleash_daemon = 1;
		} break;

      case '?':
      case 'h': {
		  printf("Usage:    %s\n"
		         "   [-p port_names]\n"
			 "   [-a port_algs]\n"
			 "   [-s port_streams]\n"
			 "   [-n graph_name]\n"
			 "   [-i input_file_path]\n"
			 "   [-t file_type]\n"
			 "   [-1 (for numeric IDs)]\n"
			 "   [-d daemon mode]\n"
			 "   [-k write algorithm states to disk]\n"
			 "   [-v write vertex name mapping to disk]\n"
			 "   [-f output directory for vertex names, alg states]\n"
			 "   [-c cap number of history files to keep per alg]  \n", argv[0]);
		  printf("Defaults:\n\tport_names: %d\n\tport_algs: %d\n\tport_streams: %d\n\tgraph_name: %s\n", port_names, port_algs, port_streams, graph_name);
		  exit(0);
		} break;

    }
  }

  /* print configuration to the terminal */
  printf("\tName: %s\n", graph_name);

  /* allocate the graph */
  struct stinger * S = stinger_shared_new(&graph_name);
  //struct stinger * S = stinger_new ();
  size_t graph_sz = S->length + sizeof(struct stinger);

  /* load edges from disk (if applicable) */
  if (input_file[0] != '\0')
  {
    switch (file_type[0])
    {
      case 'b': {
		  int64_t nv, ne;
		  int64_t *off, *ind, *weight, *graphmem;
		  snarf_graph (input_file, &nv, &ne, (int64_t**)&off,
		      (int64_t**)&ind, (int64_t**)&weight, (int64_t**)&graphmem);
		  stinger_set_initial_edges (S, nv, 0, off, ind, weight, NULL, NULL, 0);
		  free(graphmem);
		} break;  /* STINGER binary */

      case 'c': {
		  load_csv_graph (S, input_file, use_numerics);
		} break;  /* CSV */

      case 'd': {
		  load_dimacs_graph (S, input_file);
		} break;  /* DIMACS */

      case 'g': {
		} break;  /* GML / GraphML / GEXF -- you pick */

      case 'j': {
		  load_json_graph (S, input_file);
		} break;  /* JSON */

      case 'x': {
		} break;  /* XML */

      default:	{
		  printf("Unsupported file type.\n");
		  exit(0);
		} break;
    }
  }

  printf("Graph created. Running stats...");
  tic();
  printf("\n\tVertices: %ld\n\tEdges: %ld\n",
      stinger_num_active_vertices(S), stinger_total_edges(S));

  /* consistency check */
  printf("\tConsistency %ld\n", (long) stinger_consistency_check(S, S->max_nv));
  printf("\tDone. %lf seconds\n", toc());

  /* we need a socket that can reply with the shmem name & size of the graph */
  pid_t name_pid, batch_pid;

  /* child will handle name and size requests */
  name_pid = fork ();
  if (name_pid == 0)
  {
    start_udp_graph_name_server (graph_name, graph_sz, port_names);
    exit (0);
  }

  /* and we need a listener that can receive new edges */
  batch_pid = fork ();
  if (batch_pid == 0)
  {
    start_tcp_batch_server (S, graph_name, port_streams, port_algs);
    exit (0);
  }

  if(unleash_daemon) {
    while(1) { sleep(10); }
  } else {
    printf("Press <q> to shut down the server...\n");
    while (getchar() != 'q');
  }

  printf("Shutting down the name server..."); fflush(stdout);
  int status;
  kill(name_pid, SIGTERM);
  waitpid(name_pid, &status, 0);
  printf(" done.\n"); fflush(stdout);

  printf("Shutting down the batch server..."); fflush(stdout);
  kill(batch_pid, SIGTERM);
  waitpid(batch_pid, &status, 0);
  printf(" done.\n"); fflush(stdout);

  /* clean up */
  stinger_shared_free(S, graph_name, graph_sz);
  free(graph_name);
  free(input_file);

  return 0;
}
<|endoftext|>


<commit_before>#pragma once

#include "socket/udp.hpp"
#include "socket/tcp.hpp"

namespace blackhole {

namespace sink {

template<typename Protocol, typename Backend = socket::boost_backend_t<Protocol>>
class socket_t {
    Backend m_backend;

public:
    socket_t(const std::string& host, std::uint16_t port) :
        m_backend(host, port)
    {
    }

    void consume(const std::string& message) {
        m_backend.write(message);
    }

    Backend& backend() {
        return m_backend;
    }
};

} // namespace sink

} // namespace blackhole
<commit_msg>Intermediate state.<commit_after>#pragma once

#include "socket/udp.hpp"
#include "socket/tcp.hpp"

namespace blackhole {

namespace sink {

template<typename Protocol, typename Backend = socket::boost_backend_t<Protocol> >
class socket_t {
    Backend m_backend;

public:
    socket_t(const std::string& host, std::uint16_t port) :
        m_backend(host, port)
    {
    }

    void consume(const std::string& message) {
        m_backend.write(message);
    }

    Backend& backend() {
        return m_backend;
    }
};

} // namespace sink

} // namespace blackhole
<|endoftext|>


<commit_before>/* Rapicorn
 * Copyright (C) 2006 Tim Janik
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * A copy of the GNU Lesser General Public License should ship along
 * with this library; if not, see http://www.gnu.org/copyleft/.
 */
#include "scrollitemsimpl.hh"
#include "root.hh"

namespace Rapicorn {

/* --- ScrollArea --- */
ScrollArea::ScrollArea()
{}

/* --- ScrollAreaImpl --- */
ScrollAreaImpl::ScrollAreaImpl() :
  m_hadjustment (NULL), m_vadjustment (NULL)
{}

Adjustment&
ScrollAreaImpl::hadjustment () const
{
  if (!m_hadjustment)
    m_hadjustment = Adjustment::create (0, 0, 1, 0.01, 0.2);
  return *m_hadjustment;
}

Adjustment&
ScrollAreaImpl::vadjustment () const
{
  if (!m_vadjustment)
    m_vadjustment = Adjustment::create (0, 0, 1, 0.01, 0.2);
  return *m_vadjustment;
}

Adjustment*
ScrollAreaImpl::get_adjustment (AdjustmentSourceType adj_source,
                                const String        &name)
{
  switch (adj_source)
    {
    case ADJUSTMENT_SOURCE_ANCESTRY_HORIZONTAL:
      return &hadjustment();
    case ADJUSTMENT_SOURCE_ANCESTRY_VERTICAL:
      return &vadjustment();
    default:
      return NULL;
    }
}

double
ScrollAreaImpl::xoffset () const
{
  return round (hadjustment().value());
}

double
ScrollAreaImpl::yoffset () const
{
  return round (vadjustment().value());
}

void
ScrollAreaImpl::scroll_to (double x,
                           double y)
{
  hadjustment().freeze();
  vadjustment().freeze();
  m_hadjustment->value (round (x));
  m_vadjustment->flipped_value (round (y));
  m_hadjustment->constrain();
  m_vadjustment->constrain();
  m_hadjustment->thaw();
  m_vadjustment->thaw();
}

static const ItemFactory<ScrollAreaImpl> scroll_area_factory ("Rapicorn::Factory::ScrollArea");

/* --- ScrollPortImpl --- */
class ScrollPortImpl : public virtual SingleContainerImpl {
  Adjustment *m_hadjustment, *m_vadjustment;
  double m_last_xoffset, m_last_yoffset;
  virtual void
  hierarchy_changed (Item *old_toplevel)
  {
    if (m_hadjustment)
      m_hadjustment->sig_value_changed -= slot (*this, &ScrollPortImpl::adjustment_changed);
    m_hadjustment = NULL;
    if (m_vadjustment)
      m_vadjustment->sig_value_changed -= slot (*this, &ScrollPortImpl::adjustment_changed);
    m_vadjustment = NULL;
    this->SingleContainerImpl::hierarchy_changed (old_toplevel);
    if (anchored())
      {
        find_adjustments (ADJUSTMENT_SOURCE_ANCESTRY_HORIZONTAL, &m_hadjustment,
                          ADJUSTMENT_SOURCE_ANCESTRY_VERTICAL, &m_vadjustment);
        if (m_hadjustment)
          m_hadjustment->sig_value_changed += slot (*this, &ScrollPortImpl::adjustment_changed);
        if (m_vadjustment)
          m_vadjustment->sig_value_changed += slot (*this, &ScrollPortImpl::adjustment_changed);
      }
  }
  virtual void
  size_request (Requisition &requisition)
  {
    bool chspread = false, cvspread = false;
    if (has_children())
      {
        Item &child = get_child();
        if (child.allocatable())
          {
            requisition = child.size_request ();
            chspread = child.hspread();
            cvspread = child.vspread();
          }
      }
    if (0) /* ScrollPortImpl does NOT propagate h/v-spreading */
      {
        set_flag (HSPREAD_CONTAINER, chspread);
        set_flag (VSPREAD_CONTAINER, cvspread);
      }
  }
  virtual void
  size_allocate (Allocation area)
  {
    if (!has_allocatable_child())
      {
        allocation (area);
        return;
      }
    Item &child = get_child();
    Requisition rq = child.size_request();
    if (rq.width < area.width)
      {
        area.x += (area.width - rq.width) / 2;
        area.width = rq.width;
      }
    if (rq.height < area.height)
      {
        area.y += (area.height - rq.height) / 2;
        area.height = rq.height;
      }
    allocation (area);
    area.x = 0; /* 0-offset */
    area.y = 0; /* 0-offset */
    area.width = rq.width;
    area.height = rq.height;
    child.set_allocation (area);
    area = child.allocation();
    const double small_step = 10;
    if (m_hadjustment)
      {
        m_hadjustment->freeze();
        m_hadjustment->lower (0);
        m_hadjustment->upper (area.width);
        double d, w = allocation().width, l = min (w, 1);
        m_hadjustment->page (w);
        m_hadjustment->step_increment (min (small_step, round (m_hadjustment->page() / 10)));
        m_hadjustment->page_increment (max (small_step, round (m_hadjustment->page() / 3)));
        w = MIN (m_hadjustment->page(), area.width);
        d = m_hadjustment->step_increment ();
        m_hadjustment->step_increment (CLAMP (d, l, w));
        d = m_hadjustment->page_increment ();
        m_hadjustment->page_increment (CLAMP (d, l, w));
      }
    if (m_vadjustment)
      {
        m_vadjustment->freeze();
        m_vadjustment->lower (0);
        m_vadjustment->upper (area.height);
        double d, h = allocation().height, l = min (h, 1);
        m_vadjustment->page (h);
        m_vadjustment->step_increment (min (small_step, round (m_vadjustment->page() / 10)));
        m_vadjustment->page_increment (max (small_step, round (m_vadjustment->page() / 3)));
        h = MIN (m_vadjustment->page(), area.height);
        d = m_vadjustment->step_increment ();
        m_vadjustment->step_increment (CLAMP (d, l, h));
        d = m_vadjustment->page_increment ();
        m_vadjustment->page_increment (CLAMP (d, l, h));
      }
    m_last_xoffset = m_hadjustment ? round (m_hadjustment->value()) : 0.0;
    m_last_yoffset = m_vadjustment ? round (m_vadjustment->flipped_value()) : 0.0;
    if (m_hadjustment)
      m_hadjustment->constrain();
    if (m_vadjustment)
      m_vadjustment->constrain();
    if (m_hadjustment)
      m_hadjustment->thaw();
    if (m_vadjustment)
      m_vadjustment->thaw();
  }
  virtual void
  render (Display &display)
  {
    if (!has_drawable_child())
      return;
    double xoffset = m_hadjustment ? round (m_hadjustment->value()) : 0.0;
    double yoffset = m_vadjustment ? round (m_vadjustment->flipped_value()) : 0.0;
    const Allocation area = allocation();
    Item &child = get_child();
    const IRect ica = child.allocation();
    Display scroll_display;
    scroll_display.push_clip_rect (ica.x, ica.y, ica.width, ica.height);
    scroll_display.push_clip_rect (ifloor (xoffset), ifloor (yoffset), iceil (area.width), iceil (area.height));
    Rect pr = display.current_rect();
    scroll_display.push_clip_rect (ifloor (pr.x - area.x + xoffset), ifloor (pr.y - area.y + yoffset), iceil (pr.width), iceil (pr.height));
    if (!scroll_display.empty())
      {
        child.render (scroll_display);
        Plane &plane = display.create_plane (background());
        int64 real_x = plane.xstart();
        int64 real_y = plane.ystart();
        Plane::warp_plane_iknowwhatimdoing (plane,
                                            iround (real_x - area.x + xoffset),
                                            iround (real_y - area.y + yoffset));
        scroll_display.render_combined (plane);
        Plane::warp_plane_iknowwhatimdoing (plane, real_x, real_y);
      }
    scroll_display.pop_clip_rect();
    scroll_display.pop_clip_rect();
    scroll_display.pop_clip_rect();
  }
  void
  adjustment_changed()
  {
    double xoffset = m_hadjustment ? round (m_hadjustment->value()) : 0.0;
    double yoffset = m_vadjustment ? round (m_vadjustment->flipped_value()) : 0.0;
    double xdelta = xoffset - m_last_xoffset;
    double ydelta = yoffset - m_last_yoffset;
    Root *ritem = get_root();
    if (!drawable() || !ritem)
      {
        m_last_xoffset = xoffset;
        m_last_yoffset = yoffset;
      }
    else if (fabs (xdelta) + fabs (ydelta) >= 0.1)
      {
        /* copy scrolled area */
        Allocation a = allocation();
        copy_area (Rect (Point (a.x, a.y), a.width, a.height),
                   Point (a.x - xdelta, a.y - ydelta));
        /* expose areas uncovered by scrolling */
        if (xdelta > 0)
          expose (Allocation (a.x + a.width + max (-a.width, -xdelta), a.y, min (a.width, xdelta), a.height));
        else if (xdelta < 0)
          expose (Allocation (a.x, a.y, min (a.width, -xdelta), a.height));
        if (ydelta > 0)
          expose (Allocation (a.x, a.y + a.height + max (-a.height, -ydelta), a.width, min (a.height, ydelta)));
        else if (ydelta < 0)
          expose (Allocation (a.x, a.y, a.width, min (a.height, -ydelta)));
        /* update last scroll position */
        m_last_xoffset = xoffset;
        m_last_yoffset = yoffset;
      }
  }
  virtual Affine
  child_affine (const Item &item)
  {
    const Allocation area = allocation();
    double xoffset = m_hadjustment ? round (m_hadjustment->value()) : 0.0;
    double yoffset = m_vadjustment ? round (m_vadjustment->flipped_value()) : 0.0;
    return AffineTranslate (-area.x + xoffset, -area.y + yoffset);
  }
  virtual void
  set_focus_child (Item *item)
  {
    SingleContainerImpl::set_focus_child (item);
    Item *fchild = get_focus_child();
    if (!fchild)
      return;
    Root *rt = get_root();
    if (!rt)
      return;
    Item *const rfitem = rt->get_focus();
    Item *fitem = rfitem;
    if (!fitem)
      return;
    /* list focus items between focus_item and out immediate child */
    std::list<Item*> fitems;
    while (fitem)
      {
        fitems.push_back (fitem);
        if (fitem == fchild)
          break;
        fitem = fitem->parent();
      }
    /* find the first focus descendant that fits the scroll area */
    fitem = rfitem; /* fallback to innermost focus item */
    const Rect area = allocation();
    for (Walker<Item*> w = walker (fitems); w.has_next(); w++)
      {
        Item *item = *w;
        Rect a = item->allocation();
        if (!translate_from (*item, 1, &a))
          {
            fitem = NULL; // no geographic descendant
            break;
          }
        if (a.width <= area.width && a.height <= area.height)
          {
            fitem = item;
            break;
          }
      }
    if (!fitem)
      return;
    /* adjust scroll area to fitem's area */
    Rect farea = fitem->allocation();
    if (!translate_from (*fitem, 1, &farea))
      return;           // not geographic descendant
    if (0)
      printerr ("scroll-focus: area=%s farea=%s child=%p (%s)\n",
                area.string().c_str(), farea.string().c_str(), fitem,
                fitem->allocation().string().c_str());
    /* calc new scroll position, giving precedence to lower left */
    double deltax = 0, deltay = 0;
    if (farea.upper_x() > area.upper_x() + deltax)
      deltax += farea.upper_x() - (area.upper_x() + deltax);
    if (farea.x < area.x + deltax)
      deltax += farea.x - (area.x + deltax);
    if (farea.upper_y() > area.upper_y() + deltay)
      deltay += farea.upper_y() - (area.upper_y() + deltay);
    if (farea.y < area.y + deltay)
      deltay += farea.y - (area.y + deltay);
    /* scroll to new position */
    m_hadjustment->freeze();
    m_vadjustment->freeze();
    if (deltax)
      m_hadjustment->value (m_hadjustment->value() + deltax);
    if (deltay)
      m_vadjustment->flipped_value (m_vadjustment->flipped_value() + deltay);
    m_hadjustment->constrain();
    m_vadjustment->constrain();
    m_hadjustment->thaw();
    m_vadjustment->thaw();
  }
public:
  ScrollPortImpl() :
    m_hadjustment (NULL), m_vadjustment (NULL),
    m_last_xoffset (0), m_last_yoffset(0)
  {}
};
static const ItemFactory<ScrollPortImpl> scroll_port_factory ("Rapicorn::Factory::ScrollPort");

} // Rapicorn
<commit_msg>UI: render scroll port with cairo<commit_after>/* Rapicorn
 * Copyright (C) 2006 Tim Janik
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * A copy of the GNU Lesser General Public License should ship along
 * with this library; if not, see http://www.gnu.org/copyleft/.
 */
#include "scrollitemsimpl.hh"
#include "root.hh"

namespace Rapicorn {

/* --- ScrollArea --- */
ScrollArea::ScrollArea()
{}

/* --- ScrollAreaImpl --- */
ScrollAreaImpl::ScrollAreaImpl() :
  m_hadjustment (NULL), m_vadjustment (NULL)
{}

Adjustment&
ScrollAreaImpl::hadjustment () const
{
  if (!m_hadjustment)
    m_hadjustment = Adjustment::create (0, 0, 1, 0.01, 0.2);
  return *m_hadjustment;
}

Adjustment&
ScrollAreaImpl::vadjustment () const
{
  if (!m_vadjustment)
    m_vadjustment = Adjustment::create (0, 0, 1, 0.01, 0.2);
  return *m_vadjustment;
}

Adjustment*
ScrollAreaImpl::get_adjustment (AdjustmentSourceType adj_source,
                                const String        &name)
{
  switch (adj_source)
    {
    case ADJUSTMENT_SOURCE_ANCESTRY_HORIZONTAL:
      return &hadjustment();
    case ADJUSTMENT_SOURCE_ANCESTRY_VERTICAL:
      return &vadjustment();
    default:
      return NULL;
    }
}

double
ScrollAreaImpl::xoffset () const
{
  return round (hadjustment().value());
}

double
ScrollAreaImpl::yoffset () const
{
  return round (vadjustment().value());
}

void
ScrollAreaImpl::scroll_to (double x,
                           double y)
{
  hadjustment().freeze();
  vadjustment().freeze();
  m_hadjustment->value (round (x));
  m_vadjustment->flipped_value (round (y));
  m_hadjustment->constrain();
  m_vadjustment->constrain();
  m_hadjustment->thaw();
  m_vadjustment->thaw();
}

static const ItemFactory<ScrollAreaImpl> scroll_area_factory ("Rapicorn::Factory::ScrollArea");

/* --- ScrollPortImpl --- */
class ScrollPortImpl : public virtual SingleContainerImpl {
  Adjustment *m_hadjustment, *m_vadjustment;
  double m_last_xoffset, m_last_yoffset;
  virtual void
  hierarchy_changed (Item *old_toplevel)
  {
    if (m_hadjustment)
      m_hadjustment->sig_value_changed -= slot (*this, &ScrollPortImpl::adjustment_changed);
    m_hadjustment = NULL;
    if (m_vadjustment)
      m_vadjustment->sig_value_changed -= slot (*this, &ScrollPortImpl::adjustment_changed);
    m_vadjustment = NULL;
    this->SingleContainerImpl::hierarchy_changed (old_toplevel);
    if (anchored())
      {
        find_adjustments (ADJUSTMENT_SOURCE_ANCESTRY_HORIZONTAL, &m_hadjustment,
                          ADJUSTMENT_SOURCE_ANCESTRY_VERTICAL, &m_vadjustment);
        if (m_hadjustment)
          m_hadjustment->sig_value_changed += slot (*this, &ScrollPortImpl::adjustment_changed);
        if (m_vadjustment)
          m_vadjustment->sig_value_changed += slot (*this, &ScrollPortImpl::adjustment_changed);
      }
  }
  virtual void
  size_request (Requisition &requisition)
  {
    bool chspread = false, cvspread = false;
    if (has_children())
      {
        Item &child = get_child();
        if (child.allocatable())
          {
            requisition = child.size_request ();
            chspread = child.hspread();
            cvspread = child.vspread();
          }
      }
    if (0) /* ScrollPortImpl does NOT propagate h/v-spreading */
      {
        set_flag (HSPREAD_CONTAINER, chspread);
        set_flag (VSPREAD_CONTAINER, cvspread);
      }
  }
  virtual void
  size_allocate (Allocation area)
  {
    if (!has_allocatable_child())
      {
        allocation (area);
        return;
      }
    Item &child = get_child();
    Requisition rq = child.size_request();
    if (rq.width < area.width)
      {
        area.x += (area.width - rq.width) / 2;
        area.width = rq.width;
      }
    if (rq.height < area.height)
      {
        area.y += (area.height - rq.height) / 2;
        area.height = rq.height;
      }
    allocation (area);
    area.x = 0; /* 0-offset */
    area.y = 0; /* 0-offset */
    area.width = rq.width;
    area.height = rq.height;
    child.set_allocation (area);
    area = child.allocation();
    const double small_step = 10;
    if (m_hadjustment)
      {
        m_hadjustment->freeze();
        m_hadjustment->lower (0);
        m_hadjustment->upper (area.width);
        double d, w = allocation().width, l = min (w, 1);
        m_hadjustment->page (w);
        m_hadjustment->step_increment (min (small_step, round (m_hadjustment->page() / 10)));
        m_hadjustment->page_increment (max (small_step, round (m_hadjustment->page() / 3)));
        w = MIN (m_hadjustment->page(), area.width);
        d = m_hadjustment->step_increment ();
        m_hadjustment->step_increment (CLAMP (d, l, w));
        d = m_hadjustment->page_increment ();
        m_hadjustment->page_increment (CLAMP (d, l, w));
      }
    if (m_vadjustment)
      {
        m_vadjustment->freeze();
        m_vadjustment->lower (0);
        m_vadjustment->upper (area.height);
        double d, h = allocation().height, l = min (h, 1);
        m_vadjustment->page (h);
        m_vadjustment->step_increment (min (small_step, round (m_vadjustment->page() / 10)));
        m_vadjustment->page_increment (max (small_step, round (m_vadjustment->page() / 3)));
        h = MIN (m_vadjustment->page(), area.height);
        d = m_vadjustment->step_increment ();
        m_vadjustment->step_increment (CLAMP (d, l, h));
        d = m_vadjustment->page_increment ();
        m_vadjustment->page_increment (CLAMP (d, l, h));
      }
    m_last_xoffset = m_hadjustment ? round (m_hadjustment->value()) : 0.0;
    m_last_yoffset = m_vadjustment ? round (m_vadjustment->flipped_value()) : 0.0;
    if (m_hadjustment)
      m_hadjustment->constrain();
    if (m_vadjustment)
      m_vadjustment->constrain();
    if (m_hadjustment)
      m_hadjustment->thaw();
    if (m_vadjustment)
      m_vadjustment->thaw();
  }
  virtual void
  render (Display &display)
  {
    if (!has_drawable_child())
      return;
    double xoffset = m_hadjustment ? round (m_hadjustment->value()) : 0.0;
    double yoffset = m_vadjustment ? round (m_vadjustment->flipped_value()) : 0.0;
    const Allocation area = allocation();
    Item &child = get_child();
    const IRect ica = child.allocation();
    Display scroll_display;
    // constrain scroll_display to child allocation
    scroll_display.push_clip_rect (ica.x, ica.y, ica.width, ica.height);
    // constrain scroll_display to visible area
    scroll_display.push_clip_rect (ifloor (xoffset), ifloor (yoffset), iceil (area.width), iceil (area.height));
    // constrain scroll_display to expose area
    Rect pr = display.current_rect();
    scroll_display.push_clip_rect (ifloor (pr.x - area.x + xoffset), ifloor (pr.y - area.y + yoffset), iceil (pr.width), iceil (pr.height));
    if (!scroll_display.empty())
      {
        // render child
        child.render (scroll_display);
        // paint scroll area background
        cairo_t *cr = display.create_cairo (background());
        // shift by scroll offset
        cairo_translate (cr, area.x -xoffset, area.y -yoffset);
        // combine onto parent
        scroll_display.render_combined (cr);
        cairo_destroy (cr);
      }
    scroll_display.pop_clip_rect();
    scroll_display.pop_clip_rect();
    scroll_display.pop_clip_rect();
  }
  void
  adjustment_changed()
  {
    double xoffset = m_hadjustment ? round (m_hadjustment->value()) : 0.0;
    double yoffset = m_vadjustment ? round (m_vadjustment->flipped_value()) : 0.0;
    double xdelta = xoffset - m_last_xoffset;
    double ydelta = yoffset - m_last_yoffset;
    Root *ritem = get_root();
    if (!drawable() || !ritem)
      {
        m_last_xoffset = xoffset;
        m_last_yoffset = yoffset;
      }
    else if (fabs (xdelta) + fabs (ydelta) >= 0.1)
      {
        /* copy scrolled area */
        Allocation a = allocation();
        copy_area (Rect (Point (a.x, a.y), a.width, a.height),
                   Point (a.x - xdelta, a.y - ydelta));
        /* expose areas uncovered by scrolling */
        if (xdelta > 0)
          expose (Allocation (a.x + a.width + max (-a.width, -xdelta), a.y, min (a.width, xdelta), a.height));
        else if (xdelta < 0)
          expose (Allocation (a.x, a.y, min (a.width, -xdelta), a.height));
        if (ydelta > 0)
          expose (Allocation (a.x, a.y + a.height + max (-a.height, -ydelta), a.width, min (a.height, ydelta)));
        else if (ydelta < 0)
          expose (Allocation (a.x, a.y, a.width, min (a.height, -ydelta)));
        /* update last scroll position */
        m_last_xoffset = xoffset;
        m_last_yoffset = yoffset;
      }
  }
  virtual Affine
  child_affine (const Item &item)
  {
    const Allocation area = allocation();
    double xoffset = m_hadjustment ? round (m_hadjustment->value()) : 0.0;
    double yoffset = m_vadjustment ? round (m_vadjustment->flipped_value()) : 0.0;
    return AffineTranslate (-area.x + xoffset, -area.y + yoffset);
  }
  virtual void
  set_focus_child (Item *item)
  {
    SingleContainerImpl::set_focus_child (item);
    Item *fchild = get_focus_child();
    if (!fchild)
      return;
    Root *rt = get_root();
    if (!rt)
      return;
    Item *const rfitem = rt->get_focus();
    Item *fitem = rfitem;
    if (!fitem)
      return;
    /* list focus items between focus_item and out immediate child */
    std::list<Item*> fitems;
    while (fitem)
      {
        fitems.push_back (fitem);
        if (fitem == fchild)
          break;
        fitem = fitem->parent();
      }
    /* find the first focus descendant that fits the scroll area */
    fitem = rfitem; /* fallback to innermost focus item */
    const Rect area = allocation();
    for (Walker<Item*> w = walker (fitems); w.has_next(); w++)
      {
        Item *item = *w;
        Rect a = item->allocation();
        if (!translate_from (*item, 1, &a))
          {
            fitem = NULL; // no geographic descendant
            break;
          }
        if (a.width <= area.width && a.height <= area.height)
          {
            fitem = item;
            break;
          }
      }
    if (!fitem)
      return;
    /* adjust scroll area to fitem's area */
    Rect farea = fitem->allocation();
    if (!translate_from (*fitem, 1, &farea))
      return;           // not geographic descendant
    if (0)
      printerr ("scroll-focus: area=%s farea=%s child=%p (%s)\n",
                area.string().c_str(), farea.string().c_str(), fitem,
                fitem->allocation().string().c_str());
    /* calc new scroll position, giving precedence to lower left */
    double deltax = 0, deltay = 0;
    if (farea.upper_x() > area.upper_x() + deltax)
      deltax += farea.upper_x() - (area.upper_x() + deltax);
    if (farea.x < area.x + deltax)
      deltax += farea.x - (area.x + deltax);
    if (farea.upper_y() > area.upper_y() + deltay)
      deltay += farea.upper_y() - (area.upper_y() + deltay);
    if (farea.y < area.y + deltay)
      deltay += farea.y - (area.y + deltay);
    /* scroll to new position */
    m_hadjustment->freeze();
    m_vadjustment->freeze();
    if (deltax)
      m_hadjustment->value (m_hadjustment->value() + deltax);
    if (deltay)
      m_vadjustment->flipped_value (m_vadjustment->flipped_value() + deltay);
    m_hadjustment->constrain();
    m_vadjustment->constrain();
    m_hadjustment->thaw();
    m_vadjustment->thaw();
  }
public:
  ScrollPortImpl() :
    m_hadjustment (NULL), m_vadjustment (NULL),
    m_last_xoffset (0), m_last_yoffset(0)
  {}
};
static const ItemFactory<ScrollPortImpl> scroll_port_factory ("Rapicorn::Factory::ScrollPort");

} // Rapicorn
<|endoftext|>