Bachelor Thesis: University of Marburg
Bachelor Thesis: University of Marburg
Bachelor Thesis
Variability-Aware Interpretation
Author:
Jonas Pusch
October 11, 2012
Advisors:
Prof. Dr. Klaus Ostermann
University of Marburg
Department of Mathematics & Computer Science
Contents
List of Listings IV
List of Abbreviations V
1 Introduction 1
1.1 Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Contributions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 Outline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2 Variability in Programs 5
2.1 Software Product Lines . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2 Variability in Product Lines . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.3 Testing Product Lines . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.4 Variability-Aware Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.4.1 Abstract syntax trees . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.4.2 Variability representation . . . . . . . . . . . . . . . . . . . . . . 11
2.5 TypeChef Variability-Aware Parser Framework . . . . . . . . . . . . . . . 13
3 Variability-Aware Interpreter 15
3.1 Problem Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.2 Concept . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.3 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4 Evaluation 31
4.1 Method Selection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.2 Comparing Runtimes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.3 Subject Selection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
II CONTENTS
5 Future Work 41
6 Related work 42
7 Conclusion 43
LIST OF FIGURES III
List of Figures
List of Listings
List of Abbreviations
Chapter 1
Introduction
1.1 Overview
Software product lines (SPL) offer the ability to create multiple similar software
products from a shared set of features. These features satisfy the needs of a certain
domain or market segment and are connected to software components, in which they
are implemented. The goal of software product lines is to decrease maintenance-
and development times when distributing various similar software products, while
simultaneously increasing the flexibility and quality of the created product variants.
An advantage of SPL is the high level of reusability that enables developers to pick a
specific subset of features and thereby get a custom-made product variant.
To create a finished product, features from a shared pool are selected and a
generator connects software components according to the specific feature selection.
Products with the same features, thus reuse the same existing software components,
which do not have to be rewritten for every new product. Furthermore, changes to the
implementation of a feature in the shared pool affect all products that use this feature
and remove the need to carry out changes to the code of every single software product.
Therefore, software product lines as a development paradigm, obtain an increasing
relevance in economy. The ability to provide custom-made software, enables compa-
nies to extend their product range, while saving working hours helps to maximize profits.
One alternative to the brute-force approach are sampling strategies [Cabral et al.,
2010]. In this approach only a subset of all configurations is analyzed, selected
randomly or by a specific criterion. Sampling strategies solve the problem of testing
2 1.2. Contributions
all generated products, but in return they cannot provide an exhaustive result for a
whole product line. To still achieve this goal, researchers have recently drawn attention
to variability-aware analysis of SPL. Performing variability-aware analysis means, that
the variability of a product line is included in the analysis process. In contrast to other
methods, variability-aware analysis are not performed on the products of a SPL, but
rather on the not yet configured implementation of a software product line. Thus,
only one analysis is performed, that takes variability into account when needed and
redundant parts of the program are not analyzed again and again.
1.2 Contributions
1.3 Outline
Chapter 2 provides an overview of software product lines in general (Section 2.1) and how
variability can be specified in programs (Section 2.2). Afterwards, testing of software
product lines is described and the problems that occur thereby (Section 2.3). The chapter
closes with describing variability-aware analysis as an approach to address problems
concerned with product-line analysis (Section 2.4), as well as the structures needed to
perform variability-aware analysis on programs.
The variability-aware interpreter is described in Chapter 3. First, this thesis declares
a problem statement that specifies the goals associated with developing an interpreter
(Section 3.1). After that, the underlying concept (Section 3.2) and the implementation
(Section 3.3) of the interpreter itself are explained in detail.
In Chapter 4 an empirical evaluation of the variability-aware interpreter is performed.
At first, it is explained which method has been used for the evaluation and why (Section
4.1). Then, the way of comparing the approaches under test is shown (Section 4.2),
before the subjects of the evaluation are described (Section 4.3). The results of the
evaluation are shown in Section 4.5. Finally, the results are discussed (Sec. 4.6) and
possible threats to validity are mentioned (Sec. 4.6.1).
Future research targets concerning variability-aware interpretation are shown in
Chapter 5 and related work is shown in Chapter 6. Lastly, this thesis gets concluded in
Chapter 7.
4 1.3. Outline
Chapter 2. Variability in Programs 5
Chapter 2
Variability in Programs
Domain Engineering
Feature Reusable
Model Artifacts
Application Engineering
Feature
Generator Product
Selection
generator which artifacts are used in the construction process. The outcome, finally, is
a derivate of the product-line, tailored to the customer’s specific needs.
development
time / cost
traditional
development
product-line
development
number of products
• Runtime Variability
This topic includes command-line parameters, configuration files and global con-
figuration variables. Configurations are checked at runtime and there is no explicit
generator, as unused functionality is still in the source code, even if not used due
to runtime checks.
• Build Systems and Version Control Systems [Staples and Hill, 2004]
When implementing variability with build systems or version control systems, pro-
gram variants get mapped to build scripts in the case of build systems, or branches
in the case of version control systems. Existing tools provide good tool support
for this method of building SPL, but the downside on the other hand is, that de-
velopers operate on variants, not features in isolation. Merging different variants
requires high efforts and arbitrary combinations of features are not possible.
• Aspect-Oriented Programming (AOP) [AsJ, Kiczales et al., 2001]
Aspects in AOP are able to manipulate existing class hierarchies and intercept
method calls or field access without having to change existing code. This so-
called principle of obliviousness enables developers to add feature functionality as
independent aspects to an existing code base. As aspects are one-to-one mapped
to features, a product can be created by including only the selected features, and
thus aspects, in the compilation process.
• Feature-Oriented Programming (FOP) [Batory et al., 2003, Kuhlemann et al., 2007]
FOP also follows the principle of obliviousness, as an existing code base can be
refined in feature modules. Refining a class, developers can add new methods or add
code to existing methods. The idea is also to have a one-to-one mapping between
features and feature modules. When creating a product, a special composition
algorithm processes all refinements that were included by a certain feature selection.
That way, the existing code base gets modified only by the refinements, which were
chosen due to the feature selection.
• Preprocessors [Liebig et al., 2010]
Using preprocessors, the source code is transformed before the compiler processes
it. Developers specify code fragments that will either be left or removed according
to a specific configuration, when the preprocessor is called on a source file. This
8 2.3. Testing Product Lines
Concerning software product lines, testing is also important, because there are many
different program variants, that can be derived from one set of software components
and in the best case, the test result should provide an exhaustive output for the whole
product line. However, testing a whole product line is different from testing a single
program, because product lines include feature contexts next to the normal source code,
which requires a more sophisticated approach.
The easiest way to conduct unit tests on product lines is to perform them on the
products. In the Application Engineering process, the final outcome is a configured
program, which contains a subset of the product line’s shared set of software compo-
nents, according to a specific feature selection. This product does not longer contain
variability, because the generation process removes feature context declarations, after
it has been checked whether their nested code block gets included in the final product,
or not. For that reason, a default unit test can be executed on the generated product.
The results of all test executions of all products that can be derived from a product
line, provide an exhaustive test result for the product line as a whole.
When developing a product line, the number of features determines the possible
number of different products, which can be derived. Another factor, that has influence
Chapter 2. Variability in Programs 9
on the maximum number of products, are dependencies between features. For example,
when the selection of one feature implies that another feature already has been selected,
the maximum number of derivates decreases. Assuming no dependencies between
features, a product line with n optional features has 2n possible products. This leads to
huge numbers of products that have to be tested, when the number of features increases.
Performing product-line analysis by testing all possible products, therefore is declined as
a brute-force approach. High variability leads to high complexity when testing product
lines. Considering a product line with 320 optional, independent features would lead
to more possible products than the estimated number of atoms in the universe. Even
with great computing power, testing a product line with about 10000 features (210000
variants), like the Linux kernel, would not provide a result in a reasonable amount of
time, when using the brute-force approach.
A possible solution for still getting a result, when testing a product line with such
high numbers of features, is to reduce the number of products being tested. Sampling
strategies [Cabral et al., 2010] focus on selecting some of the product-line variants for
testing. Variants are selected randomly or by a specific criterion, with all criteria aiming
at providing a more meaningful result by choosing the tested variants wisely. Though
sampling strategies can provide a result, where the brute-force approach would not be
able to provide a result in an acceptable amount of time, their result is not fully exhaus-
tive. Even when choosing variants under test wisely, there is no guarantee for getting
an expressive result, because still not all variants have been investigated. Therefore,
sampling strategies do not provide an appropriate solution for getting exhaustive test
results. They only solve performance issues when trading off performance against result
quality, which is not desirable. In this work, the problem of getting an exhaustive result,
while still not checking every possible variant, is addressed.
Product3Line Product
configure
(enriched3with3Variability) (without3Variability)
1
variability-aware3 brute-force
analysis 3 2 approach
4
Result configure Result
(enriched3with3Variability) aggregate
(without3Variability)
configures all possible products (Step 1) and then conducts analysis on all of them
(Step 2). On the contrary, the green path describes the variability-aware analysis, that
is performed on the entire product line, instead of configured products (Step 3). The
outcome of the analysis process is a result enriched with variability, because it has to
store the value of all investigated properties for each configuration. Afterwards, the
result for one specific configuration can be deduced from the variability-enriched result
(Step 4, top). To get a result which describes the whole product line, it is also possible
to aggregate all results from the brute-force analysis (Step 4, bottom). Though the
results of both approaches should be equivalent, the variability-aware approach (green)
is expected to be faster than the brute-force approach (red), because common code
segments are analyzed only once.
AST
1 begin Assign If
2 x = 1;
3 Var
Id Num Greater Block
4 i f ( x > 0) {
5 y = 1 + 2; x 1 Var
Id Num Assign
6 }
7 end x 0 Var
Id Add
y Num Num
1 2
Figure 2.4: Exemplary code fragment with its corresponding AST.
Figure 2.5: Variable code fragment and the corresponding AST with variability.
Figure 2.5 continues the example from the last section, but now introduces ifdef
variability in the source code and variability nodes in the AST, respectively. Variability
nodes can be optional (green) or conditional (blue). For example, the first assignment
shall only be executed in variants with feature A selected (Lines 2-4). In the corre-
sponding AST, the assignment is wrapped with an Opt node that also includes the
feature context in which the Assign node later influences the execution (left branch).
12 2.4. Variability-Aware Analysis
Conditional nodes are used when different configurations yield different values, as
with the assignment to y (Lines 7-13). In configurations where feature B is selected,
the left side of the addition has the value 2, otherwise it yields 3, which is denoted
using a Choice construct. On the contrary, when a conditional value is equal in all
configurations, it is denoted using One, e.g. the right side of the assignment to x (Line 3).
After looking abstractly at the constructs to represent variability, the following para-
graph describes how they are realized as constructs of an object-oriented programming
language (Scala). Figure 2.6 shows, that there are two constructs available to handle
variability: Opt[T] and Conditional[T]. Opt[T] is used for optional elements of type T,
e.g. a list of statements with some of them only being present in specific configurations
( List [Opt[Statement]]). Its two fields, feature and entry, specify the presence condition, that
determines in which configurations the element is present on the one hand, and the
element of type T itself, that is wrapped by the Opt[T] class on the other hand.
Presence conditions are of type FeatureExpr, which is a class especially designed for
propositional formulas. FeatureExpr provides useful functions on propositional formulas,
e.g. querying if the formula is a tautology or a contradiction, concatenating two formulas
(and, or), negating the formula (not) or checking if a specific feature selection satisfies
the formula. True indicates, that a node is present in every configuration.
While Opt[T] manages optional elements and mainly occurs in lists, Conditional[T] is
the construct for elements, that have different values regarding to specific configura-
tions. Lists of optional elements can have an arbitrary number of elements in different
configurations, but conditional elements have exactly one value for each configuration.
Conditional[T] is realized as an abstract class, that has two subclasses. One[T] simply
is a container for elements of type T . Choice[T] is used to choose between two values
depending on a presence condition. More specifically, if the presence condition evaluates
to true, the value of this choice object for the current configuration is in the then-branch,
otherwise it is in the else-branch. thenBranch and elseBranch are of type Conditional[T]
again, which makes it possible to build nested choices. As a result, it can not only be
chosen between two values, rather specifying multiple alternative elements of type T is
possible.
• parse sequences
Parsing sequences of elements is used to parse compound constructs, such as a while
loop, which contains a keyword, a condition and a body.
Further details on how exactly variability contexts are parsed using the TypeChef
Variability-Aware Parser Framework have already been published in previous work and
are no topic in this work. How using the framework is done programmatically, is illus-
trated in Section 3.3.
14 2.5. TypeChef Variability-Aware Parser Framework
Chapter 3. Variability-Aware Interpreter 15
Chapter 3
Variability-Aware Interpreter
In order to analyze whole product lines, a sophisticated approach is needed, that provides
an exhaustive result in a reasonable amount of time. Right now, analyzing whole product
lines is performed either with brute-force analysis, that require to analyze up to 2#F eatures
configurations, or with sampling strategies, that reduce the number of tests, but cannot
yield an exhaustive result for the whole product line. When focusing on analyzing
configured products, the dependency between the number of features and the effort
needed to receive a test result remains, thus providing a bad scaling for product lines with
many features. Researchers therefore draw their attention to variability-aware analysis,
a technique to perform analysis on the product-line code itself, taking variability into
account and reducing test effort by analyzing shared code passages only once.
The above-mentioned performance problems for product lines with high numbers
of features can be transferred to the interpretation of source code. When interpreting
variability-enriched product-line code, developers want to be able to check a certain
property, for example, with an assertion. According to brute-force analysis, this problem
would be solved by interpreting every possible variant of the product line individually,
which means also executing the assertion in every variant and thus getting an exhaustive
result for the whole product line.
This method, however, leads to performance issues for product lines with many pos-
sible configurations, as has already been explained above. Therefore, the goal is to
transfer the benefits of variability-aware analysis, which already provide strategies for
reducing analysis effort in comparison to the brute-force approach, to the interpretation
of source code. The resulting approach, variability-aware interpretation, takes varability
into account when interpreting source code and as with variability-aware analysis, source
code is interpreted only once and a speedup is received by not interpreting common code
passages again and again. To perform variability-aware interpretation on source code,
an interpreter must be implemented, that supports reasoning about variability in the
manner of variability-aware analysis.
This work proposes to research the practicability and performance of such a
variability-aware interpreter that accepts a While language and that applies the prin-
ciples of variability-aware analysis to interpreting source code.
16 3.2. Concept
3.2 Concept
This section describes the concept of a variability-aware interpreter, using the principles
of variability-aware analysis to perform testing on product lines as a whole. Interpreters
are programs that execute code of a certain programming language at runtime. Tradi-
tional interpreters operate on ASTs (Sec. 2.4.1), which are the representation of a certain
code fragment. Additionally, they use a store, where the values of all used variables are
stored.
On executing a code fragment, the interpreter reads from the store, when variables are
accessed and writes to the store, when variables are assigned, thus returning an updated
store at the end of the execution. This process could be described with the following
function: execute(s: Statement, store: Store) : Store. However, when interpreting source code
in a variability-aware fashion, the components used in the interpretation process differ.
As shown in Section 2.4.2, ASTs are now enriched with variability. The interpreter also
must be aware of the variability context it is currently operating in, so this variability
context is another input for the variability-aware approach. Finally, the variability-
aware interpreter stores its variables also in a variability-aware store, that maps variable
names to conditional values, instead of just plain values. Using function notation, the
variability-aware interpretation would be described with: execute(s: Conditional[Statement],
store : VAStore): VAStore.
AST with
Variability
read & write
Variability
context Variability-Aware Variability-Aware
Interpreter Store
that the addition is performed twice, incrementing both sides of the Choice construct.
On the other hand, when incrementing x, the interpreter behaves differently. In this
case the interpreter has to evaluate Add(Var(”x”), Num(1)), but looking up x in the store
returns just One(1), which is not dependent on a certain configuration. In this case,
the variability-aware interpreter executes the addition only once, thus acting like a
traditional interpreter and saving effort compared to the brute-force approach.
Figure 3.2 illustrates, how the variability-aware interpreter handles assignments and
stores conditional values in a variability-aware store. However, to provide a more com-
prehensive approach, the variability-aware interpreter must support more language con-
structs, such as control structures. The variability-aware interpreter actually is imple-
mented to support a While language including functions. Though it is desirable to
support high-level programming languages as Java, C, C++, etc. in the future, this
work presents a prototype interpreter for initial insights on viability and performance.
Adapting the structures presented in this work, to high-level programming languages, is
left open as a future research topic.
While executing assignments is a relatively straightforward task for the variability-
aware interpreter, handling control structures requires more complicated reasoning
about variability. For example, consider a while loop, where the number of iterations
depends on the configuration. As a result, all statements in the loop body must
be executed in a specific feature context, that describes in which configurations the
current loop iteration is still performed. A more detailed example for while loops is
shown below. The following paragraphs step through all language constructs, excerpt
assignments, which have already been shown, and describe how the variability-aware
interpreter executes them, taking variability into account during interpretation.
The variability-aware interpreter executes the then branch (Line 9) in the variability
context, where the condition yields true and the else branch (Line 11) in the context,
where it yields f alse, which is the negation of the context for the then branch. Because
c is only greater than 0, when A is not selected, the context for the then branch is
¬A and the context for the else branch is A. Thus, the assignments store its values
only in the variability context, in which they have been executed, resulting in the il-
lustrated store, where res has value 2 on feature A being selected, and value 3 otherwise.
While loops. The fact that conditions can be dependent on configurations causes the
problem that while loops may have different numbers of iterations in variability-aware
interpretation. Therefore, while loops are executed until there is no further possible
configuration, in which the loop would perform another iteration. Additionally, the
statements in the loop body are executed in the variability context, where the condition
yields true. Considering a case, where the condition is true in one configuration and
f alse in a different one, the variability-aware interpreter would perform the statements
in the loop body only in the variability context with the condition being true.
Figure 3.4 shows an example for
while loops, where the condition depends
VA Store
on feature A. In every iteration, the
interpreter resolves the feature context, in
which the condition yields true. For the res
first three iterations, i > 0 is true in every
configuration. The query for the feature A A
context in which the condition holds,
therefore results in the propositional
formula true. However, in the fourth
3 4
iteration, the condition only yields true
in variability context ¬A. Due to that, Figure 3.4: Variability propagation
the incrementation of res (Line 9) is in while loops.
executed in variability context ¬A, which
propagates the variability for variable i to res. After the assignment, the value of res
has been increased by one, but only in configurations, where feature A is not selected,
which is shown in the store right to the code fragment.
ditions, which have been described in Section 2.4.2, variability contexts are represented
by the type FeatureExpr that already provides functions for concatenating propositional
formulas out of the box.
In Figure 3.5, multiple assignments are executed in different variability contexts.
The code fragment shows a block in feature context A (Lines 3-10) with two inner
assignments. On executing the block, the variability context of the block, which
is A, has to be merged with the context of its inner statements. The first inner
assignment has no local variability context, which means that its context is T rue. It
is therefore executed in variability context A ∧ T rue. The second assignment in the
block is analogously executed in variability context A ∧ B. This finally results in three
different values for x. As the illustrated store shows, x has value 2, when features
A and B are selected, value 1, if only A is selected, and 0, when none of the two is selected.
After looking at the language constructs, which are supported by the variability-aware
interpreter, a more complicated example is shown, where all of the above-mentioned con-
structs have been used. Figure 3.7 shows the partial trace of a full program execution,
allowing to see the difference between the interpretation of two exemplary configura-
tions of the program and the variability-aware interpretation. All program variables
are tracked and shown at five points of program execution. After the first assignment
(Line 2) all variables are shared between configurations, but when y is assigned, the
effects of variability get visible (Line 8). In case of the configurations, different values
are stored for y and the variability-aware interpretation stores y as a Choice. At Line
10, the condition’s value depends on the configuration, which results in the value of x
also being conditional. The last snapshot of the variables illustrates the different kinds
of results, which both approaches provide. While the configurations both have a unique
result, the variability-aware interpretation yields the result for all configurations at once.
The assertion in Line 24 checks the property res < 15, which is true in all cases. While
this outcome is obvious in case of the configurations, because their result is unique, the
variability-aware interpretation checks all values for res to be less than 15. In this case,
res can have values 0, 10 and 12. Therefore, the assertion evaluates to true.
Chapter 3. Variability-Aware Interpreter 21
B A B Variability-AwarehInterpretation
xh=h4 xh=h4 xh=hOne(4)
yh=hundef yh=hundef yh=hOne(undef)
resh=hundef resh=hundef resh=hOne(undef)
Figure 3.7: Partial trace of program execution for two specific variants and in variability-aware fashion.
Late splitting. When expensive calculations differ only slightly between configu-
rations, intermediate results may have to be calculated only once. The goal is to keep
values shared between configurations as long as possible and only split them for the
configuration-dependent part of the calculation. This effect is called late splitting and
in contrast to early joining, which is implemented as a special feature, it occurs by
construction. Late splitting is illustrated in Listing 3.2.
The code fragment shows the calcula-
tion for the sum of the first n natural num- 1 begin
3.3 Implementation
In this section the concept of the variability-aware interpreter is picked up again and its
implementation is shown. Especially, implementations of the AST classes, the parser,
the execution of statements, the evaluation of expressions and the variability-aware store
are shown. The variability-aware interpreter is written in Scala to enable compatibility
with the TypeChef libraries, which are also written in Scala.
AST
In Section 2.4.1 it has been described, how the source code of programs is represented
using ASTs. Afterwards, Section 2.4.2 showed, how variability is woven into ASTs when
the code they represent is enriched with variability. In the following, it is illustrated
how the AST nodes are implemented as constructs of Scala.
1 abstract c l a s s Stmt
2 case c l a s s A s s i g n ( n : S t r i n g , e : C o n d i t i o n a l [ Expr ] ) extends Stmt
3 case c l a s s I f ( c : C o n d i t i o n a l [ Expr ] , thn : Block , e l s : Block ) extends Stmt
4 case c l a s s While ( c : C o n d i t i o n a l [ Expr ] , b : Block ) extends Stmt
5 case c l a s s Block ( s : L i s t [ Opt [ Stmt ] ] ) extends Stmt
6 case c l a s s FuncDec ( n : S t r i n g , a : L i s t [ Opt [ S t r i n g ] ] , b : Block ) extends Stmt
7
8 abstract c l a s s Expr
9 case c l a s s Num( i : I n t ) extends Expr
10 case c l a s s Var ( name : S t r i n g ) extends Expr
11 case c l a s s C a l l ( n : S t r i n g , a : L i s t [ Opt [ Expr ] ] ) extends Expr
12 case c l a s s Add( e1 : Expr , e2 : Expr ) extends Expr
13 case c l a s s Sub ( e1 : Expr , e2 : Expr ) extends Expr
14 ...
As shown in Listing 3.3, the While language constructs are divided in statements
and expressions. Statements include Assignments, which have a field for the variable
name and an expression, which will be evaluated and stored as value for the corresponding
variable. If- and While statements have a condition, which will be evaluated before their
bodies are executed. While loops have one block statement, that includes the statements
which will be repeatedly executed. If statements have one block for their then branch
and another block for the else branch. Function declarations also count as statements
and include the function name, a list of function parameters and the function body as
a block statement. Expressions can be numbers (Num), the value of a variable (Var) or
function calls (Call). Calls have the name of the called function as field, as well as a
list of parameter expressions. Expressions can also be operators (Add/Sub/...), which
include another left and right Expression. The abstract syntax of the While language
is extended with Variability by using Opt and Conditional constructs, which have been
described in Section 2.4.2. This provides the ability to express, for example, function
declarations with configuration dependent arguments.
24 3.3. Implementation
Parser
Listing 3.4: Parsing while- and block statements with the TypeChef Parser Framework
Finally, Figure 3.8 describes the syntax of the variability-enriched While language
that is accepted by the parser of this work. A program contains a list of optional state-
ments and function declarations. It starts with ”begin” and is closed with ”end”. All
these constructs are implemented using the TypeChef Parser Framework and its helper
functions, analogously to the example presented in Listing 3.4.
Statements
After parsing the source code of a program, it is stored as a list of optional statements.
Running the program means, that the interpreter successively processes all statements
and accordingly updates its store. Listing 3.5 shows the implementation of statement
execution in the variability-aware interpreter. First of all, the interpreter does not exe-
cute any statement, if the variability context is a contradiction (Line 2). The handling
for the remaining statement types is explained in the following.
Assignments. On interpreting an assignment (Lines 4-6), at first, the right side
of the assignment is evaluated to a conditional value. Afterwards this value is saved
in the store. The value is saved as a Choice to express, that the assigned variable re-
ceives its value only in the variability context, the assignment has been called with.
Finally, if this would yield an unnecessarily complicated structure, it is simplified with
the corresponding function (Line 6). An example for this process would be simplifying
Choice(True, One(1), One(2)) to One(1).
Blocks. When interpreting blocks, all statements included in a block are executed
in the variability context of the block itself and their own context. To achieve this, the
interpreter is recursively called with variability context f e ∧ vctx (Line 8).
While loops. The concept of how while loops have to be handled concerning vari-
ability, has been described in Section 3.2. In short, every iteration of the loop needs
to query the variability context, in which the while condition yields true. Looking at
the code, this behavior is received by calling whenT rue on the result of evaluating the
condition in every iteration (Line 13). The result of whenT rue (Lines 35-40) is a feature
expression, that expresses all configurations, in which the evaluated condition is true.
The loop is exited, when there is no configuration left, in which another iteration could
be performed.
If statements. For interpreting if statements, the variability context in which the
if condition is true, is also queried (Line 19). When this context is satisfiable, the then
branch is executed with it (Line 20). If there are statements in the else branch, these
are executed in the negation of the queried context, which is the context, where the
condition yields f alse (Line 21).
Assertions. Interpreting assertions, an exception is thrown, when the variability
context of when the assertion condition yields true, is not equivalent to the context, the
assertion has been called in (Line 25).
Function declarations. When the interpreted statement is a FuncDec (Lines 29-31),
it is stored in the function store of the execution. The behavior is the same, as with
assignments to variables, which has been explained above.
26 3.3. Implementation
1 t r a i t Value {
2 def g e t I n t ( ) : I n t
3 def g e t B o o l ( ) : Boolean
4 }
5
Expressions
Choice Choice
x2 y2 02 C One One
Store
x One(0)
y Choice(C, One(1), One(2)) 12 2
Figure 3.9: Exemplary mapping over conditional structures.
Store
To map values to variables, the interpreter interacts with a variability-aware store. The
implementation of the store is shown in Listing 3.8. Basically, the store consists of a
simple Map[String, Conditional[Value]], but behaves differently, when a variable is queried,
which is not yet in the store. In this case, a single UndefVal is returned (Line 6). Also,
it is possible, to get the value of a variable in a specific variability context. This is
implemented using the TypeChef helper function f indSubtree, which looks for values
Chapter 3. Variability-Aware Interpreter 29
1 c l a s s VAStore {
2 val v a r s = Map . empty [ S t r i n g , C o n d i t i o n a l [ Value ] ]
3
4 def put ( key : S t r i n g , v a l u e : C o n d i t i o n a l [ Value ] ) = v a r s . put ( key , v a l u e )
5
Plain interpreter
Next to the variability-aware interpreter, a plain interpreter has been implemented, that
is able to interpret programs without variability contexts. Reasons for implementing a
second interpreter, are the possibility to perform comparisons between both implemen-
tations (Chapter 4) and the fact, that the existing variability-enriched AST classes can
be reused with an adjusted handling. When the plain interpreter is called, it completely
ignores variability, avoiding all checks for variability contexts and thus providing a
traditional interpreter. However, the fact that it reuses the existing AST structures
requires some modifications, which are exemplarily shown for assignments and while
loops in Listing 3.9. For example, on interpreting assignments, the interpreter assumes
unambiguous expressions (Line 3) and uses a plain store, which maps variable names
to values instead of conditional values (Map[String, Value]). Also, conditions in while
loops and if statements are unambiguous. For that reason, the evaluated condition
implies directly, whether the loop is continued or not (Lines 6-12). On the contrary, the
variability-aware interpreter would need to check in which variability context further
iterations are executed, at this point.
1 def e x e c u t e ( s : Stmt , s t o : P l a i n S t o r e , f S t o : P l a i n F u n c S t o r e ) {
2 s match {
3 case A s s i g n ( name , One ( expr ) ) => s t o . put ( name , e v a l ( expr , s t o , f S t o ) )
4 case While ( One ( c ) , s ) => {
5 var cnd = true
6 while ( cnd ) {
7 cnd = e v a l ( c , s t o , f S t o ) match {
8 case E r r o r V a l u e ( ) => f a l s e
9 case v => v . g e t B o o l
10 }
11 i f ( cnd ) e x e c u t e ( s , s t o , f S t o )
12 }
13 }
14 ...
15 }
16 }
Chapter 4
Evaluation
for evaluation purposes, because the impact of variability calculations would distort the
benchmark results, when the variability-aware interpreter was also used to execute the
variants in the brute-force approach, though these programs do not contain variability
anymore. However, when using the plain interpreter, all statements and expressions are
assumed unambiguous by construction. The interpreter does not take a variability con-
text, nor does it own any functions for processing variability. It handles statements and
expressions like a traditional interpreter, which is desired when comparing variability-
aware interpretation with the brute-force approach. The strategy we used for receiving
the correct runtime values for both approaches can be described in the following major
steps:
aware interpretation, concerning a certain product line. The speedup value describes the
factor of which the variability-aware approach is faster than the brute-force approach.
Late splitting
When calculations use a single variability context as long as possible and only split,
when variability actually affects the calculation result, this effect is called late split-
ting. In this benchmark the impact of late splitting is measured based on the code
fragment in Listing 4.3. The code fragment used for benchmarking the impact of
late splitting is very close to the code, that has been used for explaining the effect in
Section 3.2. Still, the calculation for the sum of the first n numbers is used. How-
ever, in this benchmark the number of assignments to x is increased step by step.
Starting off with no specific variability
context, for further executions another as- 1 begin
4 x = 101;
way, the impact of late splitting is ex-
5 //#e n d i f
pected to increase, because in variability- 6 //#i f d e f B
aware interpretation, the calculation is ex- 7 x = 102;
• Number of statements. The size of the test product lines is continually increased
by extending the number of statements, which are generated.
Chapter 4. Evaluation 35
• Type of statements. Not only the number of statements changes over the set of
subjects, but also the type of statements that is used. Possible types of statements
are assignments, if statements and while loops.
• Number of features. In the set of test product lines, statements are randomly
annotated with variability contexts. For this purpose, a subset of the features A-F
is used. Product lines with greater size use more features respectively.
• Usage of functions. Functions have also been incorporated in the set of test
product lines. The number of declared and used functions for a certain product
line is determined by its size, which means the number of statements. In other
words, greater product lines use more functions. Though function definitions, as
well as function calls are randomly generated with a certain variability context, the
actual function body is taken from a static set of predefined functions, e.g. sum,
sub, min, max, etc.
The generator for these product lines is written with ScalaCheck [Nilsson], a library
for automated testing and test case generation. On generating product lines, ScalaCheck
automatically covers a variety of special cases, such as using one of the above-mentioned
constructs excessively (e.g. many while loops), or on the other hand, using it not at
all (e.g. an empty product line). Therefore, the comparison of both approaches over
the whole set of generated product lines, is expected to yield a representative result for
the speedup of variability-aware interpretation. The amount of 100 random generated
product lines, has been borrowed from the standard settings of the ScalaCheck test case
generator.
4.4 Setup
The following section briefly describes
the setup used for benchmark execu- CPU : Intel Core i7 3770K (4 x 3,5GHz)
tion. All benchmarks in this work RAM : 16GB DDR3-1600
have been performed on the same LANG: Scala 2.9.2
workstation. The hardware and vir- VM : Java HotSpot(TM) Client VM (1.6.0 32)
HEAP: 512MB (default) / 1024MB (max)
tual machine settings of the worksta-
tion are shown in Figure 4.2. To min- Figure 4.2: Hardware setup of the benchmark system.
imize the impact of confounding fac-
tors, all unnecessary background processes have been closed prior to benchmark execu-
tion. Also, the garbage collector has been launched manually before each benchmark
run and one warmup run was executed before the actual measurement. Additionally,
all benchmarks were performed three times, and the results shown in the next section,
represent mean values of these three runs.
36 4.5. Results
4.5 Results
4.5.1 Single cases
Early joining
Null hypothesis (H0 ): In case of early joining, the number of used features, has no
relation to the speedup of the variability-aware interpreter.
Alternative hypothesis (H1 ): For early joining, a relation between the number of
features and the speedup of the variability-aware interpreter exists.
Late splitting
The second benchmark addressed the impact of late splitting on the speedup of the
variability-aware approach. Again, the intuition was, that an increasing number
of features affects the variability-aware interpretation speedup positively, because
calculations are mostly performed in a single variability context.
Null hypothesis (H0 ): When late splitting occurs, the number of used features, has
no relation to the speedup of the variability-aware interpreter.
10 15 20
benchmark, the variability-aware inter-
Speedup
preter crosses the mark where it performs
faster than the brute-force approach, at
a later point. With a growing number
5
of features, however, the speedup also in-
0
creases, just like in the previous bench- 0 2 4 6 8 10
Null hypothesis (H0 ): Comparing the runtimes of all 100 generated product lines
under test, the variability-aware interpreter does not perform better than the brute-force
approach.
Figure 4.5: Boxplot of runtimes for generated product lines benchmark. Some outliers omitted.
38 4.6. Discussion
15
tures directly indicated the number of
variants, because there was exactly one
statement in the variability context of each
Speedup
feature, resulting in 2#f eatures variants. In
10
this benchmark, the subjects under test
are generated randomly and it is possi-
ble, that some of the product lines yield
equal variants for different feature selec-
5
4.6 Discussion
In the evaluation, three benchmarks have been conducted to compare the variability-
aware interpreter with the brute-force approach. The first two benchmarks addressed
cases, where an advantage in favor of the variability-aware approach was expected before-
hand. The null hypotheses were rejected, because an increasing number of features indeed
affected the variability-aware interpretation speedup positively. As a consequence, the
alternative hypotheses are valid. In the third benchmark, a set of 100 generated product
lines was used to compare both approaches. Performing a Welch Two Sample t-test on
the two measurement series, yields, that they differ from each other (significance level
0.99). In other words, it is most likely, that the variability-aware interpreter is actually
faster than the brute-force approach, which also rejects the null hypothesis for this third
benchmark.
Chapter 4. Evaluation 39
Internal validity. Circumstances that would influence the benchmark results are
possible threats to internal validity. This includes an alternative explanation for the
speedup values of the variability-aware interpreter. Such a threat is the impact of con-
founding factors in a benchmark. Examples for confounding factors are background
processes, the execution of benchmarks on different systems or settings, and in some
cases garbage collection. As described in Section 4.4, this evaluation has reacted to
these threats by using the same hardware setup and java virtual machine settings for ev-
ery benchmark, as well as closing every background process, which has not been needed
for the benchmark to be carried out. Additionally, the garbage collector has been in-
voked manually before every benchmark. To control the impact of outliers, a warmup
run has been executed before the actual benchmarks and all benchmarks describe the
mean of three runs.
External validity. Factors that limit the generalizability of the approach presented
in this work are threats of external validity. In the first two benchmarks of our evaluation
we investigated favorable cases of the variability-aware interpreter. This may pose a
possible threat to external validity because a speedup was already expected beforehand.
However, these benchmarks served for providing some actual values for these cases, while
the intention of the third benchmark was to provide a representative result. In this case,
a possible threat to external validity would be, that the generated test product lines were
not suitable as a set of subjects. For controlling this threat, a special test case generator,
ScalaCheck [Nilsson], has been used to cover a variety of different cases concerning the
structure of the subjects, such as empty product lines or product lines with excessive
40 4.6. Discussion
use a certain structure, e.g. many statements of the same type. Thus, a great variety of
test cases is covered, and the set of subjects is stated as sufficiently representative.
Chapter 5. Future Work 41
Chapter 5
Future Work
Chapter 6
Related work
Chapter 7
Conclusion
in common code getting executed only once. The interpreter’s accepted language is a
simple While language and it has been shown how variability is handled in all of its
language constructs.
To measure the performance of the variability-aware interpreter, an appropriate
method for comparing the variability-aware approach to the brute-force approach has
been presented. For that reason, a plain interpreter was introduced, that ignores all
variability and represents the traditional way of interpreting source-code. For compar-
ing both approaches, three benchmarks have been conducted, two of them showing the
potential of the variability-aware approach in favorable cases, and another one over a set
of random generated product lines to provide a comprehensive comparison over a variety
of subjects.
The benchmarks results have shown, that the variability-aware interpreter outper-
forms the brute-force approach in total, especially when late splitting and early joining
can be exploited. Limitations of the variability-interpreter’s performance come into view
on interpreting very small product lines, or in general, when the effort needed for inter-
preting a certain code fragment is less than the effort needed for checking the variability
context of the execution.
These results emphasize the viability of variability-aware interpretation concerning
the used While language. However, to become an established approach for software
product-line testing, the support of object oriented programming is essential. The
variability-aware interpreter has been developed as a white-box approach from scratch.
Its implementation is extensible and offers the possibility to add further language con-
structs. Also, it has been shown that some steps to supporting object orientation have
already been done in an experimental branch.
All in all it can be said, that variability-aware interpretation offers a promising al-
ternative to traditional software product-line testing approaches, that is worth further
research.
BIBLIOGRAPHY 45
Bibliography
Martin Kuhlemann, Sven Apel, and Thomas Leich. Streamlining feature-oriented de-
signs. In Proceedings of the 6th International Conference on Software Composition,
SC’07, pages 168–175, Berlin, Heidelberg, 2007. Springer-Verlag. ISBN 3-540-77350-9,
978-3-540-77350-4.
Kim Lauenroth, Klaus Pohl, and Simon Toehning. Model checking of domain artifacts
in product line engineering. In Proceedings of the 2009 IEEE/ACM International
Conference on Automated Software Engineering, ASE ’09, pages 269–280, Washington,
DC, USA, 2009. IEEE Computer Society. ISBN 978-0-7695-3891-4.
Jörg Liebig, Sven Apel, Christian Lengauer, Christian Kästner, and Michael Schulze.
An analysis of the variability in forty preprocessor-based software product lines. In
Proceedings of the 32nd ACM/IEEE International Conference on Software Engineering
- Volume 1, ICSE ’10, pages 105–114, New York, NY, USA, 2010. ACM. ISBN 978-1-
60558-719-6.
Michael Olan. Unit testing: test early, test often. J. Comput. Sci. Coll., 19(2):319–328,
December 2003. ISSN 1937-4771.
Mark Staples and Derrick Hill. Experiences adopting software product line development
without a product line architecture. In Proceedings of the 11th Asia-Pacific Software
Engineering Conference, APSEC ’04, pages 176–183, Washington, DC, USA, 2004.
IEEE Computer Society. ISBN 0-7695-2245-9.
Thomas Thüm, Sven Apel, Christian Kästner, Martin Kuhlemann, Ina Schaefer, and
Gunter Saake. Analysis strategies for software product lines. Technical Report FIN-
004-2012, School of Computer Science, University of Magdeburg, April 2012.
47
Selbstständigkeitserklärung
Hiermit erkläre ich, dass ich die vorliegende Bachelorarbeit selbstständig und nur unter
Verwendung der angegebenen Literatur und Hilfsmittel angefertigt habe. Die aus
fremden Quellen direkt oder indirekt übernommenen Stellen sind als solche kenntlich
gemacht.
Die Arbeit wurde bisher in gleicher oder ähnlicher Form keiner anderen Prüfungsbehörde
vorgelegt und auch nicht veröffentlicht.